Skip to content

Commit

Permalink
[dev.typeparams] import stmt changes from dev.go2go
Browse files Browse the repository at this point in the history
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 <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
Trust: Robert Griesemer <gri@golang.org>
Trust: Robert Findley <rfindley@google.com>
  • Loading branch information
findleyr committed Jan 11, 2021
1 parent eb53a6c commit 1ce0854
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 55 deletions.
12 changes: 0 additions & 12 deletions src/go/types/errorcodes.go
Expand Up @@ -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.
//
Expand Down
141 changes: 99 additions & 42 deletions src/go/types/stmt.go
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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")
Expand All @@ -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)

Expand Down Expand Up @@ -761,45 +770,24 @@ 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
// (irregular assignment, cannot easily map to existing assignment checks)

Expand Down Expand Up @@ -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, ""
}
2 changes: 1 addition & 1 deletion src/go/types/testdata/stmt0.src
Expand Up @@ -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
Expand Down

0 comments on commit 1ce0854

Please sign in to comment.