From 60695272877c0dd90402090726b51ac582ff0d36 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 14 May 2019 18:00:59 -0700 Subject: [PATCH] go/*: switch back to () parentheses, fill in various missing pieces This change switches parsing back to using () parentheses for type parameters. The parser now also accepts contracts in parametrized type declarations, and top-level declared contracts as defined in the design draft: contract C(T1, T2) { ... } (Internally, they are mapped to an ast.ContractType as before.) Added more tests, incl. map.go2 from the design draft, and removed some unused parser functions. Passes parser and types tests. Known issue: Composite literals with instantiated composite literal types are not recognized properly: T(P){...} fails to parse. --- src/go/ast/ast.go | 22 +-- src/go/parser/error_test.go | 2 +- src/go/parser/parser.go | 261 +++++++++------------------------ src/go/parser/short_test.go | 47 +++--- src/go/parser/testdata/map.go2 | 111 ++++++++++++++ src/go/types/check_test.go | 1 - 6 files changed, 220 insertions(+), 224 deletions(-) create mode 100644 src/go/parser/testdata/map.go2 diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go index ea8f21c498..fb530e41e8 100644 --- a/src/go/ast/ast.go +++ b/src/go/ast/ast.go @@ -257,11 +257,12 @@ func (f *FieldList) NumFields() int { return n } -// A TypeParamList represents a list of type parameters, enclosed by parentheses. +// A TypeParamList represents a list of type parameters with contract, enclosed by parentheses. type TypeParamList struct { - Lparen token.Pos // position of "(" - Names []*Ident // type parameter names; or nil - Rparen token.Pos // position of ")" + Lparen token.Pos // position of "(" + Names []*Ident // type parameter names; or nil + Contract Expr // contract; or nil + Rparen token.Pos // position of ")" } func (t *TypeParamList) Pos() token.Pos { return t.Lparen } @@ -464,7 +465,7 @@ type ( // A ContractType node represents a contract. ContractType struct { - Contract token.Pos // position of "contract" pseudo keeyword + Contract token.Pos // position of "contract" pseudo keyword Params *TypeParamList // list of type parameters; non-nil Lbrace token.Pos // position of "{" Constraints []*Constraint // list of constraints @@ -913,11 +914,12 @@ type ( // A TypeSpec node represents a type declaration (TypeSpec production). TypeSpec struct { - Doc *CommentGroup // associated documentation; or nil - Name *Ident // type name - Assign token.Pos // position of '=', if any - Type Expr // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypes - Comment *CommentGroup // line comments; or nil + Doc *CommentGroup // associated documentation; or nil + Name *Ident // type name + TPar *TypeParamList // type parameters; or nil + Assign token.Pos // position of '=', if any + Type Expr // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypes + Comment *CommentGroup // line comments; or nil } ) diff --git a/src/go/parser/error_test.go b/src/go/parser/error_test.go index 9b79097acf..c7f93c9a56 100644 --- a/src/go/parser/error_test.go +++ b/src/go/parser/error_test.go @@ -180,7 +180,7 @@ func TestErrors(t *testing.T) { } for _, fi := range list { name := fi.Name() - if !fi.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".src") { + if !fi.IsDir() && !strings.HasPrefix(name, ".") && (strings.HasSuffix(name, ".src") || strings.HasSuffix(name, ".go2")) { checkErrors(t, filepath.Join(testdata, name), nil) } } diff --git a/src/go/parser/parser.go b/src/go/parser/parser.go index 31f650bd34..ab6175047a 100644 --- a/src/go/parser/parser.go +++ b/src/go/parser/parser.go @@ -26,7 +26,7 @@ import ( "unicode" ) -const useBrackets = true +const useBrackets = false // The parser structure holds the parser's internal state. type parser struct { @@ -706,54 +706,6 @@ func (p *parser) makeIdentList(list []ast.Expr) []*ast.Ident { return idents } -func (p *parser) parseFieldDeclOld(scope *ast.Scope) *ast.Field { - if p.trace { - defer un(trace(p, "FieldDecl")) - } - - doc := p.leadComment - - // 1st FieldDecl - // A type name used as an anonymous field looks like a field identifier. - var list []ast.Expr - for { - list = append(list, p.parseVarType(false)) - if p.tok != token.COMMA { - break - } - p.next() - } - - typ := p.tryVarType(false) - - // analyze case - var idents []*ast.Ident - if typ != nil { - // IdentifierList Type - idents = p.makeIdentList(list) - } else { - // ["*"] TypeName (AnonymousField) - typ = list[0] // we always have at least one element - if n := len(list); n > 1 { - p.errorExpected(p.pos, "type") - typ = &ast.BadExpr{From: p.pos, To: p.pos} - } else if !isTypeName(deref(typ)) { - p.errorExpected(typ.Pos(), "anonymous field") - typ = &ast.BadExpr{From: typ.Pos(), To: p.safePos(typ.End())} - } - } - - tag := p.parseOptionalTag() - - p.expectSemi() // call before accessing p.linecomment - - field := &ast.Field{Doc: doc, Names: idents, Type: typ, Tag: tag, Comment: p.lineComment} - p.declare(field, nil, scope, ast.Var, idents...) - p.resolve(typ) - - return field -} - func (p *parser) parseOptionalTag() (tag *ast.BasicLit) { if p.trace { defer un(trace(p, "OptionalTag")) @@ -966,35 +918,6 @@ func (p *parser) parsePointerType(typeContext bool) *ast.StarExpr { return &ast.StarExpr{Star: star, X: base} } -// If the result is an identifier, it is not resolved. -func (p *parser) tryVarType(isParam bool) ast.Expr { - if isParam && p.tok == token.ELLIPSIS { - pos := p.pos - p.next() - typ := p.tryIdentOrType(true) // don't use parseType so we can provide better error message - if typ != nil { - p.resolve(typ) - } else { - p.error(pos, "'...' parameter is missing type") - typ = &ast.BadExpr{From: pos, To: p.pos} - } - return &ast.Ellipsis{Ellipsis: pos, Elt: typ} - } - return p.tryIdentOrType(true) -} - -// If the result is an identifier, it is not resolved. -func (p *parser) parseVarType(isParam bool) ast.Expr { - typ := p.tryVarType(isParam) - if typ == nil { - pos := p.pos - p.errorExpected(pos, "type") - p.next() // make progress - typ = &ast.BadExpr{From: pos, To: p.pos} - } - return typ -} - func (p *parser) parseDotsType() *ast.Ellipsis { if p.trace { defer un(trace(p, "DotsType")) @@ -1025,8 +948,12 @@ func (p *parser) parseParamDeclOrNil() (f field) { f.typ = p.parseType(true) case token.LBRACK: - // name[type1, type2, ...] or name []type or name [len]type - f.name, f.typ = p.parseArrayFieldOrTypeInstance(f.name) + if useBrackets { + // name[type1, type2, ...] or name []type or name [len]type + f.name, f.typ = p.parseArrayFieldOrTypeInstance(f.name) + } else { + f.typ = p.parseType(true) + } case token.ELLIPSIS: // name ...type @@ -1154,66 +1081,7 @@ func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params [ return } -func (p *parser) parseParameterListOld(scope *ast.Scope, ellipsisOk bool) (params []*ast.Field) { - if p.trace { - defer un(trace(p, "ParameterList")) - } - - // 1st ParameterDecl - // A list of identifiers looks like a list of type names. - var list []ast.Expr - for { - list = append(list, p.parseVarType(ellipsisOk)) - if p.tok != token.COMMA { - break - } - p.next() - if p.tok == token.RPAREN { - break - } - } - - // analyze case - if typ := p.tryVarType(ellipsisOk); typ != nil { - // IdentifierList Type - idents := p.makeIdentList(list) - field := &ast.Field{Names: idents, Type: typ} - params = append(params, field) - // Go spec: The scope of an identifier denoting a function - // parameter or result variable is the function body. - p.declare(field, nil, scope, ast.Var, idents...) - p.resolve(typ) - if !p.atComma("parameter list", token.RPAREN) { - return - } - p.next() - for p.tok != token.RPAREN && p.tok != token.EOF { - idents := p.parseIdentList() - typ := p.parseVarType(ellipsisOk) - field := &ast.Field{Names: idents, Type: typ} - params = append(params, field) - // Go spec: The scope of an identifier denoting a function - // parameter or result variable is the function body. - p.declare(field, nil, scope, ast.Var, idents...) - p.resolve(typ) - if !p.atComma("parameter list", token.RPAREN) { - break - } - p.next() - } - return - } - - // Type { "," Type } (anonymous parameters) - params = make([]*ast.Field, len(list)) - for i, typ := range list { - p.resolve(typ) - params[i] = &ast.Field{Type: typ} - } - return -} - -func (p *parser) parseTypeParams(scope *ast.Scope, contractOk bool) *ast.TypeParamList { +func (p *parser) parseTypeParams(scope *ast.Scope) *ast.TypeParamList { if p.trace { defer un(trace(p, "TypeParams")) } @@ -1228,19 +1096,20 @@ func (p *parser) parseTypeParams(scope *ast.Scope, contractOk bool) *ast.TypePar } var names []*ast.Ident + var contract ast.Expr if p.tok != token.RBRACK && p.tok != token.RPAREN { names = p.parseIdentList() } - if contractOk && p.tok == token.IDENT { + if p.tok == token.IDENT { // contract - p.parseType(true) + contract = p.parseType(true) } if lbrack.IsValid() { rbrack = p.expect(token.RBRACK) } - tparams := &ast.TypeParamList{Lparen: lbrack, Names: names, Rparen: rbrack} + tparams := &ast.TypeParamList{Lparen: lbrack, Names: names, Contract: contract, Rparen: rbrack} p.declare(tparams, nil, scope, ast.Typ, names...) return tparams } @@ -1253,14 +1122,14 @@ func (p *parser) parseParameters(scope *ast.Scope, typeParamsOk, ellipsisOk bool var lparen token.Pos if useBrackets && p.tok == token.LBRACK { // assume [type T](params) syntax - tparams = p.parseTypeParams(scope, true) + tparams = p.parseTypeParams(scope) lparen = p.expect(token.LPAREN) } else { // assume (type T)(params) syntax lparen = p.expect(token.LPAREN) if p.tok == token.TYPE { p.next() - tparams = p.parseTypeParams(scope, true) + tparams = p.parseTypeParams(scope) rparen := p.expect(token.RPAREN) // fix tparams @@ -1407,14 +1276,13 @@ func (p *parser) parseChanType(typeContext bool) *ast.ChanType { return &ast.ChanType{Begin: pos, Arrow: arrow, Dir: dir, Value: value} } -// ContractType = "contract" "(" [ IdentList [ "," ] ] ")" "{" { Constraint ";" } "}" . +// ContractType = "(" [ IdentList [ "," ] ] ")" "{" { Constraint ";" } "}" . +// (The "contract" keyword is already consumed.) func (p *parser) parseContractType() *ast.ContractType { if p.trace { defer un(trace(p, "ContractType")) } - pos := p.expect(token.IDENT) // TODO(gri) check that it is "contract" - var names []*ast.Ident lparen := p.expect(token.LPAREN) scope := ast.NewScope(nil) // contract scope @@ -1437,12 +1305,11 @@ func (p *parser) parseContractType() *ast.ContractType { } rbrace := p.expect(token.RBRACE) - return &ast.ContractType{Contract: pos, Params: params, Lbrace: lbrace, Constraints: constraints, Rbrace: rbrace} + return &ast.ContractType{Params: params, Lbrace: lbrace, Constraints: constraints, Rbrace: rbrace} } -// Constraint = TypeParam TypeConstraint | TypeParam MethodName Signature | ContractTypeName "(" [ TypeList [ "," ] ] ")" . +// Constraint = TypeParam Type | TypeParam MethodName Signature | ContractTypeName "(" [ TypeList [ "," ] ] ")" . // TypeParam = Ident . -// TypeConstraint = Type | "0" | "0.0" | "0i" | "==" | "!=" . // ContractTypeName = TypeName. func (p *parser) parseConstraint() *ast.Constraint { if p.trace { @@ -1461,37 +1328,16 @@ func (p *parser) parseConstraint() *ast.Constraint { param = &ast.Ident{NamePos: tname.Pos(), Name: "_"} } + // type constraint or method var mname *ast.Ident - var typ ast.Expr - switch p.tok { - case token.INT: - typ = p.wantLit("0") - - case token.FLOAT: - typ = p.wantLit("0.0") - - case token.IMAG: - typ = p.wantLit("0i") - - case token.EQL: - mname = &ast.Ident{NamePos: p.pos, Name: "=="} - p.next() - - case token.NEQ: - mname = &ast.Ident{NamePos: p.pos, Name: "!="} - p.next() - - default: - // type constraint or method - typ = p.parseType(false) - if ident, isIdent := typ.(*ast.Ident); isIdent && p.tok == token.LPAREN { - // method - mname = ident - scope := ast.NewScope(nil) // method scope - _, params := p.parseParameters(scope, false, true) - results := p.parseResult(scope, true) - typ = &ast.FuncType{Func: token.NoPos, Params: params, Results: results} - } + typ := p.parseType(false) + if ident, isIdent := typ.(*ast.Ident); isIdent && p.tok == token.LPAREN { + // method + mname = ident + scope := ast.NewScope(nil) // method scope + _, params := p.parseParameters(scope, false, true) + results := p.parseResult(scope, true) + typ = &ast.FuncType{Func: token.NoPos, Params: params, Results: results} } // param != nil @@ -1528,13 +1374,6 @@ func (p *parser) parseTypeInstance(typ ast.Expr) *ast.CallExpr { return &ast.CallExpr{Fun: typ, Lparen: opening, Args: list, Rparen: closing} } -func (p *parser) wantLit(lit string) *ast.BasicLit { - if p.lit != lit { - p.errorExpected(p.pos, lit) - } - return p.parseOperand(false).(*ast.BasicLit) -} - // If the result is an identifier, it is not resolved. // typeContext controls whether a trailing type parameter list (opening "(") // following a type is consumed. We need this to disambiguate an expression @@ -1546,7 +1385,11 @@ func (p *parser) tryIdentOrType(typeContext bool) ast.Expr { case token.IDENT: // TODO(gri) we need to be smarter about this to avoid problems with existing code if p.lit == "contract" { - return p.parseContractType() + pos := p.pos + p.next() + typ := p.parseContractType() + typ.Contract = pos // set keyword position + return typ } typ := p.parseTypeName(nil) if typeContext && (useBrackets && p.tok == token.LBRACK || p.tok == token.LPAREN) { @@ -2032,6 +1875,7 @@ L: x = p.parseCallOrConversion(p.checkExprOrType(x)) case token.LBRACE: // TODO(gri) currently this doesn't accept instantiated types + // Use code from cmd/compile/internal/syntax/parser.go. if isLiteralType(x) && (p.exprLev >= 0 || !isTypeName(x)) { if lhs { p.resolve(x) @@ -2919,17 +2763,18 @@ func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Token, _ int) ast. if p.tok == token.TYPE { // parametrized type p.next() - scope := ast.NewScope(nil) - tparams := p.parseTypeParams(scope, false) + p.openScope() + tparams := p.parseTypeParams(p.topScope) tparams.Lparen = lbrack tparams.Rparen = p.expect(token.RBRACK) - // TODO(gri) record scope and tparams with spec + spec.TPar = tparams if p.tok == token.ASSIGN { // type alias spec.Assign = p.pos p.next() } spec.Type = p.parseType(true) + p.closeScope() } else { // array type alen := p.parseArrayLen() @@ -2944,17 +2789,18 @@ func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Token, _ int) ast. if p.tok == token.TYPE { // parametrized type p.next() - scope := ast.NewScope(nil) - tparams := p.parseTypeParams(scope, false) + p.openScope() + tparams := p.parseTypeParams(p.topScope) tparams.Lparen = lparen tparams.Rparen = p.expect(token.RPAREN) - // TODO(gri) record scope and tparams with spec + spec.TPar = tparams if p.tok == token.ASSIGN { // type alias spec.Assign = p.pos p.next() } spec.Type = p.parseType(true) + p.closeScope() } else { // parenthesized type typ := p.parseType(true) @@ -2978,6 +2824,23 @@ func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Token, _ int) ast. return spec } +func (p *parser) parseContractSpec(doc *ast.CommentGroup, _ token.Token, _ int) ast.Spec { + if p.trace { + defer un(trace(p, "ContractSpec")) + } + + // For now we represent a contract specification like a type representation. + // They cannot have "outer" type parameters, though. + ident := p.parseIdent() + typ := p.parseContractType() + spec := &ast.TypeSpec{Doc: doc, Name: ident, Type: typ} + p.declare(spec, nil, p.topScope, ast.Typ, ident) + p.expectSemi() // call before accessing p.linecomment + spec.Comment = p.lineComment + + return spec +} + func (p *parser) parseGenDecl(keyword token.Token, f parseSpecFunction) *ast.GenDecl { if p.trace { defer un(trace(p, "GenDecl("+keyword.String()+")")) @@ -3087,6 +2950,14 @@ func (p *parser) parseDecl(sync map[token.Token]bool) ast.Decl { case token.FUNC: return p.parseFuncDecl() + case token.IDENT: + // TODO(gri) we need to be smarter about this to avoid problems with existing code + if p.lit == "contract" { + f = p.parseContractSpec + break + } + fallthrough + default: pos := p.pos p.errorExpected(pos, "declaration") diff --git a/src/go/parser/short_test.go b/src/go/parser/short_test.go index 0d0358d8d3..fe83adae13 100644 --- a/src/go/parser/short_test.go +++ b/src/go/parser/short_test.go @@ -50,35 +50,43 @@ var valids = []string{ `package p; type (T = p.T; _ = struct{}; x = *T)`, `package p; type T (*int)`, `package p; type T(type P) struct { P }`, + `package p; type T(type P comparable) struct { P }`, + `package p; type T(type P comparable(P)) struct { P }`, `package p; type T(type P1, P2) struct { P1; f []P2 }`, - `package p; type T[type] struct { P }`, - `package p; type T[type P] struct { P }`, - `package p; type T[type P1, P2] struct { P1; f []P2 }`, + // `package p; type T[type] struct { P }`, + // `package p; type T[type P] struct { P }`, + // `package p; type T[type P1, P2] struct { P1; f []P2 }`, `package p; var _ = [](T(int)){}`, `package p; var _ = func()T(nil)`, `package p; func _(type)()`, `package p; func _(type)()()`, - `package p; func _(T[P])`, + `package p; func _(T (P))`, + `package p; func _((T(P)))`, `package p; func _(T []E)`, `package p; func _(T [P]E)`, - `package p; func _(T[P1, P2, P3])`, + `package p; func _(x T(P1, P2, P3))`, + `package p; func _((T(P1, P2, P3)))`, `package p; func _(type A, B)(a A) B`, `package p; func _(type A, B C)(a A) B`, `package p; func _(type A, B C(A, B))(a A) B`, - `package p; type _ struct { T[P] }`, - `package p; type _ struct { T []E }`, - `package p; type _ struct { T [P]E }`, - `package p; type _ struct { imported.T[P] }`, - `package p; type _ struct { imported.T[P1, P2] }`, - `package p; func _[type]()`, - `package p; func _[type]()()`, - `package p; func _[type A, B](a A) B`, - `package p; func _[type A, B C](a A) B`, - `package p; func _[type A, B C(A, B)](a A) B`, + // `package p; type _ struct { T[P] }`, + // `package p; type _ struct { T []E }`, + // `package p; type _ struct { T [P]E }`, + // `package p; type _ struct { imported.T[P] }`, + // `package p; type _ struct { imported.T[P1, P2] }`, + // `package p; func _[type]()`, + // `package p; func _[type]()()`, + // `package p; func _[type A, B](a A) B`, + // `package p; func _[type A, B C](a A) B`, + // `package p; func _[type A, B C(A, B)](a A) B`, + `package p; contract C(){}`, + `package p; contract C(T, S, R,){}`, + `package p; contract C(T){ T (m(x, int)); }`, + `package p; contract (C1(){}; C2(){})`, `package p; type C contract(){}`, `package p; type C contract(T, S, R,){}`, `package p; type C contract(T){ T (m(x, int)); }`, - `package p; type C contract(T){ T int; T imported.T; T chan<-int; T 0.0; T ==; T m(x int) float64; C0(); imported.C1(int, T,) }`, + `package p; type C contract(T){ T int; T imported.T; T chan<-int; T m(x int) float64; C0(); imported.C1(int, T,) }`, } func TestValid(t *testing.T) { @@ -87,6 +95,12 @@ func TestValid(t *testing.T) { } } +// TestSingle is useful to track down a problem with a single short test program. +func TestSingle(t *testing.T) { + const src = `package p; func _((T(a, b,)))` + checkErrors(t, src, src) +} + var invalids = []string{ `foo /* ERROR "expected 'package'" */ !`, `package p; func f() { if { /* ERROR "missing condition" */ } };`, @@ -136,7 +150,6 @@ var invalids = []string{ //`package p; func f(x func(), u v func /* ERROR "missing ','" */ ()){}`, `package p; type C contract(T, T /* ERROR "T redeclared" */ ) {}`, `package p; type C contract(T) { imported /* ERROR "expected type parameter name" */ .T int }`, - `package p; type C contract(T) { T 00.i /* ERROR "expected 0i" */ }`, // issue 8656 `package p; func f() (a b string /* ERROR "missing ','" */ , ok bool)`, diff --git a/src/go/parser/testdata/map.go2 b/src/go/parser/testdata/map.go2 new file mode 100644 index 0000000000..036663c1c7 --- /dev/null +++ b/src/go/parser/testdata/map.go2 @@ -0,0 +1,111 @@ +// Package orderedmap provides an ordered map, implemented as a binary tree. +package orderedmap + +import "chans" + +// Map is an ordered map. +type Map(type K, V) struct { + root *node(K, V) + compare func(K, K) int +} + +// node is the type of a node in the binary tree. +type node(type K, V) struct { + key K + val V + left, right *node(K, V) +} + +// New returns a new map. +func New(type K, V)(compare func(K, K) int) *Map(K, V) { + // TODO + // return &Map(K, V){compare: compare} +} + +// find looks up key in the map, and returns either a pointer +// to the node holding key, or a pointer to the location where +// such a node would go. +func (m *Map(K, V)) find(key K) **node(K, V) { + pn := &m.root + for *pn != nil { + switch cmp := m.compare(key, (*pn).key); { + case cmp < 0: + pn = &(*pn).left + case cmp > 0: + pn = &(*pn).right + default: + return pn + } + } + return pn +} + +// Insert inserts a new key/value into the map. +// If the key is already present, the value is replaced. +// Returns true if this is a new key, false if already present. +func (m *Map(K, V)) Insert(key K, val V) bool { + pn := m.find(key) + if *pn != nil { + (*pn).val = val + return false + } + // TODO + // *pn = &node(K, V){key: key, val: val} + return true +} + +// Find returns the value associated with a key, or zero if not present. +// The found result reports whether the key was found. +func (m *Map(K, V)) Find(key K) (V, bool) { + pn := m.find(key) + if *pn == nil { + var zero V // see the discussion of zero values, above + return zero, false + } + return (*pn).val, true +} + +// keyValue is a pair of key and value used when iterating. +type keyValue(type K, V) struct { + key K + val V +} + +// InOrder returns an iterator that does an in-order traversal of the map. +func (m *Map(K, V)) InOrder() *Iterator(K, V) { + sender, receiver := chans.Ranger(keyValue(K, V))() + var f func(*node(K, V)) bool + f = func(n *node(K, V)) bool { + if n == nil { + return true + } + // Stop sending values if sender.Send returns false, + // meaning that nothing is listening at the receiver end. + return f(n.left) && + // TODO + // sender.Send(keyValue(K, V){n.key, n.val}) && + f(n.right) + } + go func() { + f(m.root) + sender.Close() + }() + return &Iterator{receiver} +} + +// Iterator is used to iterate over the map. +type Iterator(type K, V) struct { + r *chans.Receiver(keyValue(K, V)) +} + +// Next returns the next key and value pair, and a boolean indicating +// whether they are valid or whether we have reached the end. +func (it *Iterator(K, V)) Next() (K, V, bool) { + keyval, ok := it.r.Next() + if !ok { + var zerok K + var zerov V + return zerok, zerov, false + } + return keyval.key, keyval.val, true +} diff --git a/src/go/types/check_test.go b/src/go/types/check_test.go index 87e47de4d4..a4a89559e1 100644 --- a/src/go/types/check_test.go +++ b/src/go/types/check_test.go @@ -99,7 +99,6 @@ var tests = [][]string{ {"testdata/issue28251.src"}, {"testdata/issue6977.src"}, {"testdata/typeparams.src"}, - {"testdata/typeparams2.src"}, } var fset = token.NewFileSet()