go/types, types2, go/ast, go/parser: remove support for type lists

This is a rough port of CL 354131 to go/* libraries, though in practice
I just tried to reconcile any places where the phrase "type list"
occurred in the source. This resulted in adjusting quite a bit more code
than initially expected, including a few lingering cases in the
compiler.

Change-Id: Ie62a9e1aeb831b73931bc4c78bbb6ccb24f53fb0
Reviewed-on: https://go-review.googlesource.com/c/go/+/359135
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Robert Findley 2021-10-27 11:14:24 -04:00
parent a5a423e0e8
commit 925ea2dfc4
28 changed files with 65 additions and 219 deletions

View File

@ -116,7 +116,7 @@ func (check *Checker) interfaceType(ityp *Interface, iface *syntax.InterfaceType
}
// f.Name != nil
// We have a method with name f.Name, or a type of a type list (f.Name.Value == "type").
// We have a method with name f.Name.
name := f.Name.Value
if name == "_" {
if check.conf.CompilerErrorMessages {

View File

@ -44,7 +44,7 @@ func isNumeric(typ Type) bool { return is(typ, IsNumeric) }
func isString(typ Type) bool { return is(typ, IsString) }
// Note that if typ is a type parameter, isInteger(typ) || isFloat(typ) does not
// produce the expected result because a type list that contains both an integer
// produce the expected result because a type set that contains both an integer
// and a floating-point type is neither (all) integers, nor (all) floats.
// Use isIntegerOrFloat instead.
func isIntegerOrFloat(typ Type) bool { return is(typ, IsInteger|IsFloat) }

View File

@ -172,7 +172,7 @@ type _ interface {
~struct{f int} | ~struct{g int} | ~struct /* ERROR overlapping terms */ {f int}
}
// Interface type lists can contain any type, incl. *Named types.
// Interface term lists can contain any type, incl. *Named types.
// Verify that we use the underlying type to compute the operational type.
type MyInt int
func add1[T interface{MyInt}](x T) T {
@ -184,9 +184,9 @@ func double[T interface{MyInt|MyString}](x T) T {
return x + x
}
// Embedding of interfaces with type lists leads to interfaces
// with type lists that are the intersection of the embedded
// type lists.
// Embedding of interfaces with term lists leads to interfaces
// with term lists that are the intersection of the embedded
// term lists.
type E0 interface {
~int | ~bool | ~string
@ -277,4 +277,4 @@ func _[T none]() {
_ = gg[T]
_ = hh[int]
_ = hh[T]
}
}

View File

@ -276,8 +276,8 @@ func _() {
// Type parameters are never const types, i.e., it's
// not possible to declare a constant of type parameter type.
// (If a type list contains just a single const type, we could
// allow it, but such type lists don't make much sense in the
// (If a type set contains just a single const type, we could
// allow it, but such type sets don't make much sense in the
// first place.)
func _[T interface{~int|~float64}]() {
// not valid

View File

@ -4,10 +4,10 @@
package p
// Do not report a duplicate type error for this type list.
// Do not report a duplicate type error for this term list.
// (Check types after interfaces have been completed.)
type _ interface {
// TODO(gri) Once we have full type sets we can enable this again.
// Fow now we don't permit interfaces in type lists.
// Fow now we don't permit interfaces in term lists.
// type interface{ Error() string }, interface{ String() string }
}

View File

@ -21,7 +21,7 @@ func f[P1, P2, P3 any](x1 P1, x2 P2, x3 P3) struct{}
func f[P interface{}](x P)
func f[P1, P2, P3 interface {
m1(P1)
type P2, P3
~P2 | ~P3
}](x1 P1, x2 P2, x3 P3) struct{}
func f[P any](T1[P], T2[P]) T3[P]

View File

@ -19,7 +19,7 @@ func f[P any](x P)
func f[P1, P2, P3 any](x1 P1, x2 P2, x3 P3) struct{}
func f[P interface{}](x P)
func f[P1, P2, P3 interface{ m1(P1); type P2, P3 }](x1 P1, x2 P2, x3 P3) struct{}
func f[P1, P2, P3 interface{ m1(P1); ~P2|~P3 }](x1 P1, x2 P2, x3 P3) struct{}
func f[P any](T1[P], T2[P]) T3[P]
func (x T[P]) m()

View File

@ -193,14 +193,10 @@ func isDirective(c string) bool {
// in a signature.
// Field.Names is nil for unnamed parameters (parameter lists which only contain types)
// and embedded struct fields. In the latter case, the field name is the type name.
// Field.Names contains a single name "type" for elements of interface type lists.
// Types belonging to the same type list share the same "type" identifier which also
// records the position of that keyword.
//
type Field struct {
Doc *CommentGroup // associated documentation; or nil
Names []*Ident // field/method/(type) parameter names, or type "type"; or nil
Type Expr // field/method/parameter type, type list type; or nil
Names []*Ident // field/method/(type) parameter names; or nil
Type Expr // field/method/parameter type; or nil
Tag *BasicLit // field tag; or nil
Comment *CommentGroup // line comments; or nil
}

View File

@ -1128,18 +1128,6 @@ parseElements:
p.expectSemi()
comment := p.lineComment
list = append(list, &ast.Field{Type: typ, Comment: comment})
case p.tok == token.TYPE && p.allowGenerics():
// TODO(rfindley): remove TypeList syntax and refactor the clauses above.
// all types in a type list share the same field name "type"
// (since type is a keyword, a Go program cannot have that field name)
name := []*ast.Ident{{NamePos: p.pos, Name: "type"}}
p.next()
// add each type as a field named "type"
for _, typ := range p.parseTypeList() {
list = append(list, &ast.Field{Names: name, Type: typ})
}
p.expectSemi()
case p.allowGenerics():
if t := p.tryIdentOrType(); t != nil {
typ := p.embeddedElem(t)

View File

@ -117,12 +117,6 @@ func (r *resolver) closeLabelScope() {
func (r *resolver) declare(decl, data interface{}, scope *ast.Scope, kind ast.ObjKind, idents ...*ast.Ident) {
for _, ident := range idents {
// "type" is used for type lists in interfaces, and is otherwise an invalid
// identifier. The 'type' identifier is also artificially duplicated in the
// type list, so could cause panics below if we were to proceed.
if ident.Name == "type" {
continue
}
assert(ident.Obj == nil, "identifier already declared or resolved")
obj := ast.NewObj(kind, ident.Name)
// remember the corresponding declaration for redeclaration
@ -188,10 +182,9 @@ func (r *resolver) resolve(ident *ast.Ident, collectUnresolved bool) {
if ident.Obj != nil {
panic(fmt.Sprintf("%s: identifier %s already declared or resolved", r.handle.Position(ident.Pos()), ident.Name))
}
// '_' and 'type' should never refer to existing declarations: '_' because it
// has special handling in the spec, and 'type' because it is a keyword, and
// only valid in an interface type list.
if ident.Name == "_" || ident.Name == "type" {
// '_' should never refer to existing declarations, because it has special
// handling in the spec.
if ident.Name == "_" {
return
}
for s := r.topScope; s != nil; s = s.Outer {

View File

@ -119,8 +119,7 @@ var validWithTParamsOnly = []string{
`package p; func _(T[P] /* ERROR "missing element type" */ ) T[P]`,
`package p; type _ struct{ T[P] /* ERROR "missing element type" */ }`,
`package p; type _ struct{ T[struct /* ERROR "expected expression" */ {a, b, c int}] }`,
`package p; type _ interface{type /* ERROR "expected '}', found 'type'" */ int}`,
`package p; type _ interface{type /* ERROR "expected '}', found 'type'" */ int, float32; type bool; m(); type string;}`,
`package p; type _ interface{int| /* ERROR "expected ';'" */ float32; bool; m(); string;}`,
`package p; type I1[T any /* ERROR "expected ']', found any" */ ] interface{}; type I2 interface{ I1[int] }`,
`package p; type I1[T any /* ERROR "expected ']', found any" */ ] interface{}; type I2[T any] interface{ I1[T] }`,
`package p; type _ interface { f[ /* ERROR "expected ';', found '\['" */ T any]() }`,

View File

@ -4,16 +4,13 @@
// This file contains test cases for interfaces containing
// constraint elements.
//
// For now, we accept both ordinary type lists and the
// more complex constraint elements.
package p
type _ interface {
m()
type int
type int, string
~int
~int|string
E
}
@ -31,7 +28,6 @@ type _ interface {
T[int, string] | string
int | ~T[string, struct{}]
~int | ~string
type bool, int, float64
}
type _ interface {

View File

@ -9,10 +9,10 @@ import "math"
// Numeric is type bound that matches any numeric type.
// It would likely be in a constraints package in the standard library.
type Numeric interface {
type int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr,
float32, float64,
complex64, complex128
~int|~int8|~int16|~int32|~int64|
~uint|~uint8|~uint16|~uint32|~uint64|~uintptr|
~float32|~float64|
~complex64|~complex128
}
func DotProduct[T Numeric](s1, s2 []T) T {
@ -42,14 +42,14 @@ func AbsDifference[T NumericAbs](a, b T) T {
// OrderedNumeric is a type bound that matches numeric types that support the < operator.
type OrderedNumeric interface {
type int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr,
float32, float64
~int|~int8|~int16|~int32|~int64|
~uint|~uint8|~uint16|~uint32|~uint64|~uintptr|
~float32|~float64
}
// Complex is a type bound that matches the two complex types, which do not have a < operator.
type Complex interface {
type complex64, complex128
~complex64|~complex128
}
// OrderedAbs is a helper type that defines an Abs method for

View File

@ -15,7 +15,7 @@ type Pair /* =@Pair */ [L /* =@L */, R /* =@R */ any] struct {
var _ = Pair /* @Pair */ [int, string]{}
type Addable /* =@Addable */ interface {
type int64, float64
~int64|~float64
}
func Add /* =@AddDecl */[T /* =@T */ Addable /* @Addable */](l /* =@l */, r /* =@r */ T /* @T */) T /* @T */ {
@ -30,7 +30,7 @@ type Receiver /* =@Receiver */[P /* =@P */ any] struct {}
// parameter below.
func (r /* =@recv */ Receiver /* @Receiver */ [P]) m() P {}
func f /* =@f */[T1 /* =@T1 */ interface{type []T2 /* @T2 */}, T2 /* =@T2 */ any](
func f /* =@f */[T1 /* =@T1 */ interface{~[]T2 /* @T2 */}, T2 /* =@T2 */ any](
x /* =@x */ T1 /* @T1 */, T1 /* =@T1_duplicate */ y, // Note that this is a bug:
// the duplicate T1 should
// not be allowed.

View File

@ -471,17 +471,9 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool)
p.expr(f.Type)
} else { // interface
if len(f.Names) > 0 {
// type list type or method
name := f.Names[0] // "type" or method name
name := f.Names[0] // method name
p.expr(name)
if name.Name == "type" {
// type list type
p.print(blank)
p.expr(f.Type)
} else {
// method
p.signature(f.Type.(*ast.FuncType)) // don't print "func"
}
p.signature(f.Type.(*ast.FuncType)) // don't print "func"
} else {
// embedded interface
p.expr(f.Type)
@ -568,24 +560,10 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool)
p.setComment(f.Doc)
p.recordLine(&line)
if name != nil {
// type list type or method
if name.Name == "type" {
// type list type
if name == prev {
// type is part of a list of types
p.print(token.COMMA, blank)
} else {
// type starts a new list of types
p.print(name, blank)
}
p.expr(f.Type)
prev = name
} else {
// method
p.expr(name)
p.signature(f.Type.(*ast.FuncType)) // don't print "func"
prev = nil
}
// method
p.expr(name)
p.signature(f.Type.(*ast.FuncType)) // don't print "func"
prev = nil
} else {
// embedded interface
p.expr(f.Type)

View File

@ -22,7 +22,7 @@ func f[P1, P2, P3 any](x1 P1, x2 P2, x3 P3) struct{}
func f[P interface{}](x P)
func f[P1, P2, P3 interface {
m1(P1)
type P2, P3
~P2 | ~P3
}](x1 P1, x2 P2, x3 P3) struct{}
func f[P any](T1[P], T2[P]) T3[P]
@ -35,14 +35,6 @@ func _() {
_ = []T[P]{}
}
// properly format one-line type lists
// TODO(rfindley): remove support for type lists
type _ interface{ type a }
type _ interface {
type a, b, c
}
// type constraint literals with elided interfaces
func _[P ~int, Q int | string]() {}
func _[P struct{ f int }, Q *P]() {}

View File

@ -20,7 +20,7 @@ func f[P any](x P)
func f[P1, P2, P3 any](x1 P1, x2 P2, x3 P3) struct{}
func f[P interface{}](x P)
func f[P1, P2, P3 interface{ m1(P1); type P2, P3 }](x1 P1, x2 P2, x3 P3) struct{}
func f[P1, P2, P3 interface{ m1(P1); ~P2|~P3 }](x1 P1, x2 P2, x3 P3) struct{}
func f[P any](T1[P], T2[P]) T3[P]
func (x T[P]) m()
@ -32,12 +32,6 @@ func _() {
_ = []T[P]{}
}
// properly format one-line type lists
// TODO(rfindley): remove support for type lists
type _ interface { type a }
type _ interface { type a,b,c }
// type constraint literals with elided interfaces
func _[P ~int, Q int | string]() {}
func _[P struct{f int}, Q *P]() {}

View File

@ -141,6 +141,7 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind {
}
if t := asInterface(T); t != nil {
if !t.IsMethodSet() {
// TODO(rfindley): remove the phrase "type list" from this error.
check.errorf(call, _Todo, "cannot use interface %s in conversion (contains type list or is comparable)", T)
break
}

View File

@ -145,29 +145,8 @@ func WriteExpr(buf *bytes.Buffer, x ast.Expr) {
writeSigExpr(buf, x)
case *ast.InterfaceType:
// separate type list types from method list
// TODO(gri) we can get rid of this extra code if writeExprList does the separation
var types []ast.Expr
var methods []*ast.Field
for _, f := range x.Methods.List {
if len(f.Names) > 1 && f.Names[0].Name == "type" {
// type list type
types = append(types, f.Type)
} else {
// method or embedded interface
methods = append(methods, f)
}
}
buf.WriteString("interface{")
writeFieldList(buf, methods, "; ", true)
if len(types) > 0 {
if len(methods) > 0 {
buf.WriteString("; ")
}
buf.WriteString("type ")
writeExprList(buf, types)
}
writeFieldList(buf, x.Methods.List, "; ", true)
buf.WriteByte('}')
case *ast.MapType:

View File

@ -36,15 +36,8 @@ var testExprs = []testEntry{
dup("func(int, float32) string"),
dup("interface{m()}"),
dup("interface{m() string; n(x int)}"),
dup("interface{type int}"),
dup("interface{~int}"),
// The following exprs do not get formatted correctly: each element in the
// type list is printed on a separate line. This is left as a placeholder
// until type lists are removed.
// TODO(rfindley): remove this once type lists are gone.
// dup("interface{type int, float64, string}"),
// dup("interface{type int; m()}"),
// dup("interface{type int, float64, string; m() string; n(x int)}"),
dup("map[string]int"),
dup("chan E"),
dup("<-chan E"),

View File

@ -142,9 +142,6 @@ func (t *Interface) String() string { return TypeString(t, nil) }
// Implementation
func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, def *Named) {
var tlist []ast.Expr
var tname *ast.Ident // "type" name of first entry in a type list declaration
addEmbedded := func(pos token.Pos, typ Type) {
ityp.embeddeds = append(ityp.embeddeds, typ)
if ityp.embedPos == nil {
@ -158,41 +155,15 @@ func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, d
addEmbedded(f.Type.Pos(), parseUnion(check, flattenUnion(nil, f.Type)))
continue
}
// f.Name != nil
// We have a method with name f.Names[0], or a type
// of a type list (name.Name == "type").
// (The parser ensures that there's only one method
// and we don't care if a constructed AST has more.)
// We have a method with name f.Names[0].
name := f.Names[0]
if name.Name == "_" {
check.errorf(name, _BlankIfaceMethod, "invalid method name _")
continue // ignore
}
// TODO(rfindley) Remove type list handling once the parser doesn't accept type lists anymore.
if name.Name == "type" {
// Report an error for the first type list per interface
// if we don't allow type lists, but continue.
if !allowTypeLists && tlist == nil {
check.softErrorf(name, _Todo, "use generalized embedding syntax instead of a type list")
}
// For now, collect all type list entries as if it
// were a single union, where each union element is
// of the form ~T.
// TODO(rfindley) remove once we disallow type lists
op := new(ast.UnaryExpr)
op.Op = token.TILDE
op.X = f.Type
tlist = append(tlist, op)
// Report an error if we have multiple type lists in an
// interface, but only if they are permitted in the first place.
if allowTypeLists && tname != nil && tname != name {
check.errorf(name, _Todo, "cannot have multiple type lists in an interface")
}
tname = name
continue
}
typ := check.typ(f.Type)
sig, _ := typ.(*Signature)
if sig == nil {
@ -225,13 +196,6 @@ func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, d
ityp.methods = append(ityp.methods, m)
}
// type constraints
if tlist != nil {
// TODO(rfindley): this differs from types2 due to the use of Pos() below,
// which should actually be on the ~. Confirm that this position is correct.
addEmbedded(tlist[0].Pos(), parseUnion(check, tlist))
}
// All methods and embedded elements for this interface are collected;
// i.e., this interface may be used in a type set computation.
ityp.complete = true

View File

@ -45,7 +45,7 @@ func isNumeric(typ Type) bool { return is(typ, IsNumeric) }
func isString(typ Type) bool { return is(typ, IsString) }
// Note that if typ is a type parameter, isInteger(typ) || isFloat(typ) does not
// produce the expected result because a type list that contains both an integer
// produce the expected result because a type set that contains both an integer
// and a floating-point type is neither (all) integers, nor (all) floats.
// Use isIntegerOrFloat instead.
func isIntegerOrFloat(typ Type) bool { return is(typ, IsInteger|IsFloat) }

View File

@ -53,8 +53,8 @@ func _() {
}
// When a type parameter is used as an argument to instantiate a parameterized
// type with a type list constraint, all of the type argument's types in its
// bound, but at least one (!), must be in the type list of the bound of the
// type with a type set constraint, all of the type argument's types in its
// bound, but at least one (!), must be in the type set of the bound of the
// corresponding parameterized type's type parameter.
type T1[P interface{~uint}] struct{}
@ -152,7 +152,7 @@ type inf2 /* ERROR illegal cycle */ [T any] struct{ inf2[T] }
// The implementation of conversions T(x) between integers and floating-point
// numbers checks that both T and x have either integer or floating-point
// type. When the type of T or x is a type parameter, the respective simple
// predicate disjunction in the implementation was wrong because if a type list
// predicate disjunction in the implementation was wrong because if a term list
// contains both an integer and a floating-point type, the type parameter is
// neither an integer or a floating-point number.
func convert[T1, T2 interface{~int | ~uint | ~float32}](v T1) T2 {
@ -185,13 +185,13 @@ func _[T interface{}, PT interface{~*T}] (x T) PT {
return &x
}
// Indexing of generic types containing type parameters in their type list:
// Indexing of generic types containing type parameters in their term list:
func at[T interface{ ~[]E }, E interface{}](x T, i int) E {
return x[i]
}
// A generic type inside a function acts like a named type. Its underlying
// type is itself, its "operational type" is defined by the type list in
// type is itself, its "operational type" is defined by the term list in
// the tybe bound, if any.
func _[T interface{~int}](x T) {
type myint int
@ -224,9 +224,9 @@ func _[T interface{ ~func() }](f T) {
go f()
}
// We must compare against the underlying type of type list entries
// We must compare against the underlying type of term list entries
// when checking if a constraint is satisfied by a type. The under-
// lying type of each type list entry must be computed after the
// lying type of each term list entry must be computed after the
// interface has been instantiated as its typelist may contain a
// type parameter that was substituted with a defined type.
// Test case from an (originally) failing example.

View File

@ -159,10 +159,7 @@ type _ interface {
~rune
}
// Interface type lists may contain each type at most once.
// (If there are multiple lists, we assume the author intended
// for them to be all in a single list, and we report the error
// as well.)
// Type sets may contain each type at most once.
type _ interface {
~int|~ /* ERROR overlapping terms ~int */ int
~int|int /* ERROR overlapping terms int */
@ -173,7 +170,7 @@ type _ interface {
~struct{f int} | ~struct{g int} | ~ /* ERROR overlapping terms */ struct {f int}
}
// Interface type lists can contain any type, incl. *Named types.
// Interface term lists can contain any type, incl. *Named types.
// Verify that we use the underlying type to compute the operational type.
type MyInt int
func add1[T interface{MyInt}](x T) T {
@ -185,9 +182,9 @@ func double[T interface{MyInt|MyString}](x T) T {
return x + x
}
// Embedding of interfaces with type lists leads to interfaces
// with type lists that are the intersection of the embedded
// type lists.
// Embedding of interfaces with term lists leads to interfaces
// with term lists that are the intersection of the embedded
// term lists.
type E0 interface {
~int | ~bool | ~string

View File

@ -6,18 +6,6 @@
package p
type (
// Type lists are processed as unions but an error is reported.
// TODO(gri) remove this once the parser doesn't accept type lists anymore.
_ interface{
type /* ERROR use generalized embedding syntax instead of a type list */ int
}
_ interface{
type /* ERROR use generalized embedding syntax instead of a type list */ int
type float32
}
)
type MyInt int
type (

View File

@ -281,8 +281,8 @@ func _() {
// Type parameters are never const types, i.e., it's
// not possible to declare a constant of type parameter type.
// (If a type list contains just a single const type, we could
// allow it, but such type lists don't make much sense in the
// (If a type set contains just a single const type, we could
// allow it, but such type sets don't make much sense in the
// first place.)
func _[T interface {~int|~float64}]() {
// not valid

View File

@ -4,10 +4,10 @@
package p
// Do not report a duplicate type error for this type list.
// Do not report a duplicate type error for this term list.
// (Check types after interfaces have been completed.)
type _ interface {
// TODO(rfindley) Once we have full type sets we can enable this again.
// Fow now we don't permit interfaces in type lists.
// Fow now we don't permit interfaces in term lists.
// type interface{ Error() string }, interface{ String() string }
}

View File

@ -78,28 +78,16 @@ func parseUnion(check *Checker, tlist []ast.Expr) Type {
continue
}
x := tlist[i]
pos := x.Pos()
// We may not know the position of x if it was a typechecker-
// introduced ~T term for a type list entry T. Use the position
// of T instead.
// TODO(rfindley) remove this test once we don't support type lists anymore
if !pos.IsValid() {
if op, _ := x.(*ast.UnaryExpr); op != nil {
pos = op.X.Pos()
}
}
u := under(t.typ)
f, _ := u.(*Interface)
if t.tilde {
if f != nil {
check.errorf(x, _Todo, "invalid use of ~ (%s is an interface)", t.typ)
check.errorf(tlist[i], _Todo, "invalid use of ~ (%s is an interface)", t.typ)
continue // don't report another error for t
}
if !Identical(u, t.typ) {
check.errorf(x, _Todo, "invalid use of ~ (underlying type of %s is %s)", t.typ, u)
check.errorf(tlist[i], _Todo, "invalid use of ~ (underlying type of %s is %s)", t.typ, u)
continue // don't report another error for t
}
}
@ -108,14 +96,14 @@ func parseUnion(check *Checker, tlist []ast.Expr) Type {
// in the beginning. Embedded interfaces with tilde are excluded above. If we reach
// here, we must have at least two terms in the union.
if f != nil && !f.typeSet().IsTypeSet() {
check.errorf(atPos(pos), _Todo, "cannot use %s in union (interface contains methods)", t)
check.errorf(tlist[i], _Todo, "cannot use %s in union (interface contains methods)", t)
continue // don't report another error for t
}
// Report overlapping (non-disjoint) terms such as
// a|a, a|~a, ~a|~a, and ~a|A (where under(A) == a).
if j := overlappingTerm(terms[:i], t); j >= 0 {
check.softErrorf(atPos(pos), _Todo, "overlapping terms %s and %s", t, terms[j])
check.softErrorf(tlist[i], _Todo, "overlapping terms %s and %s", t, terms[j])
}
}
})