From 1ce08541574f749947400a051bb40c8352743887 Mon Sep 17 00:00:00 2001 From: Rob Findley Date: Thu, 7 Jan 2021 11:13:56 -0500 Subject: [PATCH] [dev.typeparams] import stmt changes from dev.go2go Import logic for typechecking statements involving generics from the dev.go2go branch. Notably, range type checking was simplified in dev.go2go, resulting in the removal of the _InvalidChanRange error code. Change-Id: I84c2665226c2b9b74e85f7fb6df257b0a292e5d3 Reviewed-on: https://go-review.googlesource.com/c/go/+/282120 Run-TryBot: Robert Findley TryBot-Result: Go Bot Reviewed-by: Robert Griesemer Trust: Robert Griesemer Trust: Robert Findley --- src/go/types/errorcodes.go | 12 --- src/go/types/stmt.go | 143 ++++++++++++++++++++++---------- src/go/types/testdata/stmt0.src | 2 +- 3 files changed, 101 insertions(+), 56 deletions(-) diff --git a/src/go/types/errorcodes.go b/src/go/types/errorcodes.go index 897b34d74f..2c5a291660 100644 --- a/src/go/types/errorcodes.go +++ b/src/go/types/errorcodes.go @@ -1038,18 +1038,6 @@ const ( // } _InvalidPostDecl - // _InvalidChanRange occurs when a send-only channel used in a range - // expression. - // - // Example: - // func sum(c chan<- int) { - // s := 0 - // for i := range c { - // s += i - // } - // } - _InvalidChanRange - // _InvalidIterVar occurs when two iteration variables are used while ranging // over a channel. // diff --git a/src/go/types/stmt.go b/src/go/types/stmt.go index 0162368a64..82c21c2a7a 100644 --- a/src/go/types/stmt.go +++ b/src/go/types/stmt.go @@ -49,6 +49,11 @@ func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body check.error(atPos(body.Rbrace), _MissingReturn, "missing return") } + // TODO(gri) Should we make it an error to declare generic functions + // where the type parameters are not used? + // 12/19/2018: Probably not - it can make sense to have an API with + // all functions uniformly sharing the same type parameters. + // spec: "Implementation restriction: A compiler may make it illegal to // declare a variable inside a function body if the variable is never used." check.usage(sig.scope) @@ -147,9 +152,9 @@ func (check *Checker) multipleDefaults(list []ast.Stmt) { } } -func (check *Checker) openScope(s ast.Node, comment string) { - scope := NewScope(check.scope, s.Pos(), s.End(), comment) - check.recordScope(s, scope) +func (check *Checker) openScope(node ast.Node, comment string) { + scope := NewScope(check.scope, node.Pos(), node.End(), comment) + check.recordScope(node, scope) check.scope = scope } @@ -273,6 +278,9 @@ L: if T == Typ[Invalid] { continue L } + if T != nil { + check.ordinaryType(e, T) + } // look for duplicate types // (quadratic algorithm, but type switches tend to be reasonably small) for t, other := range seen { @@ -355,8 +363,8 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { return } - tch, ok := ch.typ.Underlying().(*Chan) - if !ok { + tch := asChan(ch.typ) + if tch == nil { check.invalidOp(inNode(s, s.Arrow), _InvalidSend, "cannot send to non-chan type %s", ch.typ) return } @@ -609,7 +617,7 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { return } - // rhs must be of the form: expr.(type) and expr must be an interface + // rhs must be of the form: expr.(type) and expr must be an ordinary interface expr, _ := rhs.(*ast.TypeAssertExpr) if expr == nil || expr.Type != nil { check.invalidAST(s, "incorrect form of type switch guard") @@ -620,11 +628,12 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { if x.mode == invalid { return } - xtyp, _ := x.typ.Underlying().(*Interface) + xtyp, _ := under(x.typ).(*Interface) if xtyp == nil { check.errorf(&x, _InvalidTypeSwitch, "%s is not an interface", &x) return } + check.ordinaryType(&x, xtyp) check.multipleDefaults(s.Body.List) @@ -761,43 +770,22 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { // determine key/value types var key, val Type if x.mode != invalid { - switch typ := x.typ.Underlying().(type) { - case *Basic: - if isString(typ) { - key = Typ[Int] - val = universeRune // use 'rune' name - } - case *Array: - key = Typ[Int] - val = typ.elem - case *Slice: - key = Typ[Int] - val = typ.elem - case *Pointer: - if typ, _ := typ.base.Underlying().(*Array); typ != nil { - key = Typ[Int] - val = typ.elem - } - case *Map: - key = typ.key - val = typ.elem - case *Chan: - key = typ.elem - val = Typ[Invalid] - if typ.dir == SendOnly { - check.errorf(&x, _InvalidChanRange, "cannot range over send-only channel %s", &x) - // ok to continue - } - if s.Value != nil { - check.errorf(atPos(s.Value.Pos()), _InvalidIterVar, "iteration over %s permits only one iteration variable", &x) - // ok to continue - } + typ := optype(x.typ) + if _, ok := typ.(*Chan); ok && s.Value != nil { + // TODO(gri) this also needs to happen for channels in generic variables + check.softErrorf(atPos(s.Value.Pos()), _InvalidIterVar, "range over %s permits only one iteration variable", &x) + // ok to continue + } + var msg string + key, val, msg = rangeKeyVal(typ, isVarName(s.Key), isVarName(s.Value)) + if key == nil || msg != "" { + if msg != "" { + // TODO(rFindley) should this be parenthesized, to be consistent with other qualifiers? + msg = ": " + msg + } + check.softErrorf(&x, _InvalidRangeExpr, "cannot range over %s%s", &x, msg) + // ok to continue } - } - - if key == nil { - check.errorf(&x, _InvalidRangeExpr, "cannot range over %s", &x) - // ok to continue } // check assignment to/declaration of iteration variables @@ -879,3 +867,72 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { check.invalidAST(s, "invalid statement") } } + +// isVarName reports whether x is a non-nil, non-blank (_) expression. +func isVarName(x ast.Expr) bool { + if x == nil { + return false + } + ident, _ := unparen(x).(*ast.Ident) + return ident == nil || ident.Name != "_" +} + +// rangeKeyVal returns the key and value type produced by a range clause +// over an expression of type typ, and possibly an error message. If the +// range clause is not permitted the returned key is nil or msg is not +// empty (in that case we still may have a non-nil key type which can be +// used to reduce the chance for follow-on errors). +// The wantKey, wantVal, and hasVal flags indicate which of the iteration +// variables are used or present; this matters if we range over a generic +// type where not all keys or values are of the same type. +func rangeKeyVal(typ Type, wantKey, wantVal bool) (Type, Type, string) { + switch typ := typ.(type) { + case *Basic: + if isString(typ) { + return Typ[Int], universeRune, "" // use 'rune' name + } + case *Array: + return Typ[Int], typ.elem, "" + case *Slice: + return Typ[Int], typ.elem, "" + case *Pointer: + if typ := asArray(typ.base); typ != nil { + return Typ[Int], typ.elem, "" + } + case *Map: + return typ.key, typ.elem, "" + case *Chan: + var msg string + if typ.dir == SendOnly { + msg = "send-only channel" + } + return typ.elem, Typ[Invalid], msg + case *Sum: + first := true + var key, val Type + var msg string + typ.is(func(t Type) bool { + k, v, m := rangeKeyVal(under(t), wantKey, wantVal) + if k == nil || m != "" { + key, val, msg = k, v, m + return false + } + if first { + key, val, msg = k, v, m + first = false + return true + } + if wantKey && !Identical(key, k) { + key, val, msg = nil, nil, "all possible values must have the same key type" + return false + } + if wantVal && !Identical(val, v) { + key, val, msg = nil, nil, "all possible values must have the same element type" + return false + } + return true + }) + return key, val, msg + } + return nil, nil, "" +} diff --git a/src/go/types/testdata/stmt0.src b/src/go/types/testdata/stmt0.src index fde846962e..297e34be42 100644 --- a/src/go/types/testdata/stmt0.src +++ b/src/go/types/testdata/stmt0.src @@ -886,7 +886,7 @@ func rangeloops1() { ee = e _ = ee } - for _ = range sc /* ERROR "cannot range over send-only channel" */ {} + for _ = range sc /* ERROR "cannot range over" */ {} for _ = range rc {} // constant strings