From 36c5f902f9049b82da50ac66049371830e6de031 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 7 Apr 2021 17:47:14 -0700 Subject: [PATCH] cmd/compile/internal/types2: factor out index/slice expr handling First step towards lightening the load of Checker.exprInternal by factoring out the code for index and slice expressions; incl. moving a couple of related methods (Checker.index, Checker.indexedElts). The code for handling index/slice expressions is copied 1:1 but occurrences of "goto Error" are replaced by "x.mode = invalid" followed by a "return". Change-Id: I44048dcc4851dc5e24f5f169c17f536a37a6a676 Reviewed-on: https://go-review.googlesource.com/c/go/+/308370 Trust: Robert Griesemer Run-TryBot: Robert Griesemer TryBot-Result: Go Bot Reviewed-by: Robert Findley --- src/cmd/compile/internal/types2/expr.go | 376 +-------------------- src/cmd/compile/internal/types2/index.go | 405 +++++++++++++++++++++++ 2 files changed, 407 insertions(+), 374 deletions(-) create mode 100644 src/cmd/compile/internal/types2/index.go diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go index 7d2240d41e..4a2e658a63 100644 --- a/src/cmd/compile/internal/types2/expr.go +++ b/src/cmd/compile/internal/types2/expr.go @@ -1043,104 +1043,6 @@ func (check *Checker) binary(x *operand, e syntax.Expr, lhs, rhs syntax.Expr, op // x.typ is unchanged } -// index checks an index expression for validity. -// If max >= 0, it is the upper bound for index. -// If the result typ is != Typ[Invalid], index is valid and typ is its (possibly named) integer type. -// If the result val >= 0, index is valid and val is its constant int value. -func (check *Checker) index(index syntax.Expr, max int64) (typ Type, val int64) { - typ = Typ[Invalid] - val = -1 - - var x operand - check.expr(&x, index) - if x.mode == invalid { - return - } - - // an untyped constant must be representable as Int - check.convertUntyped(&x, Typ[Int]) - if x.mode == invalid { - return - } - - // the index must be of integer type - if !isInteger(x.typ) { - check.errorf(&x, invalidArg+"index %s must be integer", &x) - return - } - - if x.mode != constant_ { - return x.typ, -1 - } - - // a constant index i must be in bounds - if constant.Sign(x.val) < 0 { - check.errorf(&x, invalidArg+"index %s must not be negative", &x) - return - } - - v, valid := constant.Int64Val(constant.ToInt(x.val)) - if !valid || max >= 0 && v >= max { - if check.conf.CompilerErrorMessages { - check.errorf(&x, "array index %s out of bounds [0:%d]", x.val.String(), max) - } else { - check.errorf(&x, "index %s is out of bounds", &x) - } - return - } - - // 0 <= v [ && v < max ] - return Typ[Int], v -} - -// indexElts checks the elements (elts) of an array or slice composite literal -// against the literal's element type (typ), and the element indices against -// the literal length if known (length >= 0). It returns the length of the -// literal (maximum index value + 1). -// -func (check *Checker) indexedElts(elts []syntax.Expr, typ Type, length int64) int64 { - visited := make(map[int64]bool, len(elts)) - var index, max int64 - for _, e := range elts { - // determine and check index - validIndex := false - eval := e - if kv, _ := e.(*syntax.KeyValueExpr); kv != nil { - if typ, i := check.index(kv.Key, length); typ != Typ[Invalid] { - if i >= 0 { - index = i - validIndex = true - } else { - check.errorf(e, "index %s must be integer constant", kv.Key) - } - } - eval = kv.Value - } else if length >= 0 && index >= length { - check.errorf(e, "index %d is out of bounds (>= %d)", index, length) - } else { - validIndex = true - } - - // if we have a valid index, check for duplicate entries - if validIndex { - if visited[index] { - check.errorf(e, "duplicate index %d in array or slice literal", index) - } - visited[index] = true - } - index++ - if index > max { - max = index - } - - // check element against composite literal element type - var x operand - check.exprWithHint(&x, eval, typ) - check.assignment(&x, typ, "array or slice literal") - } - return max -} - // exprKind describes the kind of an expression; the kind // determines if an expression is valid in 'statement context'. type exprKind int @@ -1485,291 +1387,17 @@ func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKin check.selector(x, e) case *syntax.IndexExpr: - check.exprOrType(x, e.X) + check.indexExpr(x, e) if x.mode == invalid { - check.use(e.Index) goto Error } - if x.mode == typexpr { - // type instantiation - x.mode = invalid - x.typ = check.varType(e) - if x.typ != Typ[Invalid] { - x.mode = typexpr - } - return expression - } - - if x.mode == value { - if sig := asSignature(x.typ); sig != nil && len(sig.tparams) > 0 { - // function instantiation - check.funcInst(x, e) - return expression - } - } - - // ordinary index expression - valid := false - length := int64(-1) // valid if >= 0 - switch typ := optype(x.typ).(type) { - case *Basic: - if isString(typ) { - valid = true - if x.mode == constant_ { - length = int64(len(constant.StringVal(x.val))) - } - // an indexed string always yields a byte value - // (not a constant) even if the string and the - // index are constant - x.mode = value - x.typ = universeByte // use 'byte' name - } - - case *Array: - valid = true - length = typ.len - if x.mode != variable { - x.mode = value - } - x.typ = typ.elem - - case *Pointer: - if typ := asArray(typ.base); typ != nil { - valid = true - length = typ.len - x.mode = variable - x.typ = typ.elem - } - - case *Slice: - valid = true - x.mode = variable - x.typ = typ.elem - - case *Map: - var key operand - check.expr(&key, e.Index) - check.assignment(&key, typ.key, "map index") - // ok to continue even if indexing failed - map element type is known - x.mode = mapindex - x.typ = typ.elem - x.expr = e - return expression - - case *Sum: - // A sum type can be indexed if all of the sum's types - // support indexing and have the same index and element - // type. Special rules apply for maps in the sum type. - var tkey, telem Type // key is for map types only - nmaps := 0 // number of map types in sum type - if typ.is(func(t Type) bool { - var e Type - switch t := under(t).(type) { - case *Basic: - if isString(t) { - e = universeByte - } - case *Array: - e = t.elem - case *Pointer: - if t := asArray(t.base); t != nil { - e = t.elem - } - case *Slice: - e = t.elem - case *Map: - // If there are multiple maps in the sum type, - // they must have identical key types. - // TODO(gri) We may be able to relax this rule - // but it becomes complicated very quickly. - if tkey != nil && !Identical(t.key, tkey) { - return false - } - tkey = t.key - e = t.elem - nmaps++ - case *TypeParam: - check.errorf(x, "type of %s contains a type parameter - cannot index (implementation restriction)", x) - case *instance: - panic("unimplemented") - } - if e == nil || telem != nil && !Identical(e, telem) { - return false - } - telem = e - return true - }) { - // If there are maps, the index expression must be assignable - // to the map key type (as for simple map index expressions). - if nmaps > 0 { - var key operand - check.expr(&key, e.Index) - check.assignment(&key, tkey, "map index") - // ok to continue even if indexing failed - map element type is known - - // If there are only maps, we are done. - if nmaps == len(typ.types) { - x.mode = mapindex - x.typ = telem - x.expr = e - return expression - } - - // Otherwise we have mix of maps and other types. For - // now we require that the map key be an integer type. - // TODO(gri) This is probably not good enough. - valid = isInteger(tkey) - // avoid 2nd indexing error if indexing failed above - if !valid && key.mode == invalid { - goto Error - } - x.mode = value // map index expressions are not addressable - } else { - // no maps - valid = true - x.mode = variable - } - x.typ = telem - } - } - - if !valid { - check.errorf(x, invalidOp+"cannot index %s", x) - goto Error - } - - if e.Index == nil { - check.errorf(e, invalidAST+"missing index for %s", x) - goto Error - } - - index := e.Index - if l, _ := index.(*syntax.ListExpr); l != nil { - if n := len(l.ElemList); n <= 1 { - check.errorf(e, invalidAST+"invalid use of ListExpr for index expression %v with %d indices", e, n) - goto Error - } - // len(l.ElemList) > 1 - check.error(l.ElemList[1], invalidOp+"more than one index") - index = l.ElemList[0] // continue with first index - } - - // In pathological (invalid) cases (e.g.: type T1 [][[]T1{}[0][0]]T0) - // the element type may be accessed before it's set. Make sure we have - // a valid type. - if x.typ == nil { - x.typ = Typ[Invalid] - } - - check.index(index, length) - // ok to continue - case *syntax.SliceExpr: - check.expr(x, e.X) + check.sliceExpr(x, e) if x.mode == invalid { - check.use(e.Index[:]...) goto Error } - valid := false - length := int64(-1) // valid if >= 0 - switch typ := optype(x.typ).(type) { - case *Basic: - if isString(typ) { - if e.Full { - check.error(x, invalidOp+"3-index slice of string") - goto Error - } - valid = true - if x.mode == constant_ { - length = int64(len(constant.StringVal(x.val))) - } - // spec: "For untyped string operands the result - // is a non-constant value of type string." - if typ.kind == UntypedString { - x.typ = Typ[String] - } - } - - case *Array: - valid = true - length = typ.len - if x.mode != variable { - check.errorf(x, invalidOp+"%s (slice of unaddressable value)", x) - goto Error - } - x.typ = &Slice{elem: typ.elem} - - case *Pointer: - if typ := asArray(typ.base); typ != nil { - valid = true - length = typ.len - x.typ = &Slice{elem: typ.elem} - } - - case *Slice: - valid = true - // x.typ doesn't change - - case *Sum, *TypeParam: - check.error(x, "generic slice expressions not yet implemented") - goto Error - } - - if !valid { - check.errorf(x, invalidOp+"cannot slice %s", x) - goto Error - } - - x.mode = value - - // spec: "Only the first index may be omitted; it defaults to 0." - if e.Full && (e.Index[1] == nil || e.Index[2] == nil) { - check.error(e, invalidAST+"2nd and 3rd index required in 3-index slice") - goto Error - } - - // check indices - var ind [3]int64 - for i, expr := range e.Index { - x := int64(-1) - switch { - case expr != nil: - // The "capacity" is only known statically for strings, arrays, - // and pointers to arrays, and it is the same as the length for - // those types. - max := int64(-1) - if length >= 0 { - max = length + 1 - } - if _, v := check.index(expr, max); v >= 0 { - x = v - } - case i == 0: - // default is 0 for the first index - x = 0 - case length >= 0: - // default is length (== capacity) otherwise - x = length - } - ind[i] = x - } - - // constant indices must be in range - // (check.index already checks that existing indices >= 0) - L: - for i, x := range ind[:len(ind)-1] { - if x > 0 { - for _, y := range ind[i+1:] { - if y >= 0 && x > y { - check.errorf(e, "invalid slice indices: %d > %d", x, y) - break L // only report one error, ok to continue - } - } - } - } - case *syntax.AssertExpr: check.expr(x, e.X) if x.mode == invalid { diff --git a/src/cmd/compile/internal/types2/index.go b/src/cmd/compile/internal/types2/index.go new file mode 100644 index 0000000000..0f4adab237 --- /dev/null +++ b/src/cmd/compile/internal/types2/index.go @@ -0,0 +1,405 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements typechecking of index/slice expressions. + +package types2 + +import ( + "cmd/compile/internal/syntax" + "go/constant" +) + +func (check *Checker) indexExpr(x *operand, e *syntax.IndexExpr) { + check.exprOrType(x, e.X) + if x.mode == invalid { + check.use(e.Index) + return + } + + if x.mode == typexpr { + // type instantiation + x.mode = invalid + x.typ = check.varType(e) + if x.typ != Typ[Invalid] { + x.mode = typexpr + } + return + } + + if x.mode == value { + if sig := asSignature(x.typ); sig != nil && len(sig.tparams) > 0 { + // function instantiation + check.funcInst(x, e) + return + } + } + + // ordinary index expression + valid := false + length := int64(-1) // valid if >= 0 + switch typ := optype(x.typ).(type) { + case *Basic: + if isString(typ) { + valid = true + if x.mode == constant_ { + length = int64(len(constant.StringVal(x.val))) + } + // an indexed string always yields a byte value + // (not a constant) even if the string and the + // index are constant + x.mode = value + x.typ = universeByte // use 'byte' name + } + + case *Array: + valid = true + length = typ.len + if x.mode != variable { + x.mode = value + } + x.typ = typ.elem + + case *Pointer: + if typ := asArray(typ.base); typ != nil { + valid = true + length = typ.len + x.mode = variable + x.typ = typ.elem + } + + case *Slice: + valid = true + x.mode = variable + x.typ = typ.elem + + case *Map: + var key operand + check.expr(&key, e.Index) + check.assignment(&key, typ.key, "map index") + // ok to continue even if indexing failed - map element type is known + x.mode = mapindex + x.typ = typ.elem + x.expr = e + return + + case *Sum: + // A sum type can be indexed if all of the sum's types + // support indexing and have the same index and element + // type. Special rules apply for maps in the sum type. + var tkey, telem Type // key is for map types only + nmaps := 0 // number of map types in sum type + if typ.is(func(t Type) bool { + var e Type + switch t := under(t).(type) { + case *Basic: + if isString(t) { + e = universeByte + } + case *Array: + e = t.elem + case *Pointer: + if t := asArray(t.base); t != nil { + e = t.elem + } + case *Slice: + e = t.elem + case *Map: + // If there are multiple maps in the sum type, + // they must have identical key types. + // TODO(gri) We may be able to relax this rule + // but it becomes complicated very quickly. + if tkey != nil && !Identical(t.key, tkey) { + return false + } + tkey = t.key + e = t.elem + nmaps++ + case *TypeParam: + check.errorf(x, "type of %s contains a type parameter - cannot index (implementation restriction)", x) + case *instance: + panic("unimplemented") + } + if e == nil || telem != nil && !Identical(e, telem) { + return false + } + telem = e + return true + }) { + // If there are maps, the index expression must be assignable + // to the map key type (as for simple map index expressions). + if nmaps > 0 { + var key operand + check.expr(&key, e.Index) + check.assignment(&key, tkey, "map index") + // ok to continue even if indexing failed - map element type is known + + // If there are only maps, we are done. + if nmaps == len(typ.types) { + x.mode = mapindex + x.typ = telem + x.expr = e + return + } + + // Otherwise we have mix of maps and other types. For + // now we require that the map key be an integer type. + // TODO(gri) This is probably not good enough. + valid = isInteger(tkey) + // avoid 2nd indexing error if indexing failed above + if !valid && key.mode == invalid { + x.mode = invalid + return + } + x.mode = value // map index expressions are not addressable + } else { + // no maps + valid = true + x.mode = variable + } + x.typ = telem + } + } + + if !valid { + check.errorf(x, invalidOp+"cannot index %s", x) + x.mode = invalid + return + } + + if e.Index == nil { + check.errorf(e, invalidAST+"missing index for %s", x) + x.mode = invalid + return + } + + index := e.Index + if l, _ := index.(*syntax.ListExpr); l != nil { + if n := len(l.ElemList); n <= 1 { + check.errorf(e, invalidAST+"invalid use of ListExpr for index expression %v with %d indices", e, n) + x.mode = invalid + return + } + // len(l.ElemList) > 1 + check.error(l.ElemList[1], invalidOp+"more than one index") + index = l.ElemList[0] // continue with first index + } + + // In pathological (invalid) cases (e.g.: type T1 [][[]T1{}[0][0]]T0) + // the element type may be accessed before it's set. Make sure we have + // a valid type. + if x.typ == nil { + x.typ = Typ[Invalid] + } + + check.index(index, length) +} + +func (check *Checker) sliceExpr(x *operand, e *syntax.SliceExpr) { + check.expr(x, e.X) + if x.mode == invalid { + check.use(e.Index[:]...) + return + } + + valid := false + length := int64(-1) // valid if >= 0 + switch typ := optype(x.typ).(type) { + case *Basic: + if isString(typ) { + if e.Full { + check.error(x, invalidOp+"3-index slice of string") + x.mode = invalid + return + } + valid = true + if x.mode == constant_ { + length = int64(len(constant.StringVal(x.val))) + } + // spec: "For untyped string operands the result + // is a non-constant value of type string." + if typ.kind == UntypedString { + x.typ = Typ[String] + } + } + + case *Array: + valid = true + length = typ.len + if x.mode != variable { + check.errorf(x, invalidOp+"%s (slice of unaddressable value)", x) + x.mode = invalid + return + } + x.typ = &Slice{elem: typ.elem} + + case *Pointer: + if typ := asArray(typ.base); typ != nil { + valid = true + length = typ.len + x.typ = &Slice{elem: typ.elem} + } + + case *Slice: + valid = true + // x.typ doesn't change + + case *Sum, *TypeParam: + check.error(x, "generic slice expressions not yet implemented") + x.mode = invalid + return + } + + if !valid { + check.errorf(x, invalidOp+"cannot slice %s", x) + x.mode = invalid + return + } + + x.mode = value + + // spec: "Only the first index may be omitted; it defaults to 0." + if e.Full && (e.Index[1] == nil || e.Index[2] == nil) { + check.error(e, invalidAST+"2nd and 3rd index required in 3-index slice") + x.mode = invalid + return + } + + // check indices + var ind [3]int64 + for i, expr := range e.Index { + x := int64(-1) + switch { + case expr != nil: + // The "capacity" is only known statically for strings, arrays, + // and pointers to arrays, and it is the same as the length for + // those types. + max := int64(-1) + if length >= 0 { + max = length + 1 + } + if _, v := check.index(expr, max); v >= 0 { + x = v + } + case i == 0: + // default is 0 for the first index + x = 0 + case length >= 0: + // default is length (== capacity) otherwise + x = length + } + ind[i] = x + } + + // constant indices must be in range + // (check.index already checks that existing indices >= 0) +L: + for i, x := range ind[:len(ind)-1] { + if x > 0 { + for _, y := range ind[i+1:] { + if y >= 0 && x > y { + check.errorf(e, "invalid slice indices: %d > %d", x, y) + break L // only report one error, ok to continue + } + } + } + } +} + +// index checks an index expression for validity. +// If max >= 0, it is the upper bound for index. +// If the result typ is != Typ[Invalid], index is valid and typ is its (possibly named) integer type. +// If the result val >= 0, index is valid and val is its constant int value. +func (check *Checker) index(index syntax.Expr, max int64) (typ Type, val int64) { + typ = Typ[Invalid] + val = -1 + + var x operand + check.expr(&x, index) + if x.mode == invalid { + return + } + + // an untyped constant must be representable as Int + check.convertUntyped(&x, Typ[Int]) + if x.mode == invalid { + return + } + + // the index must be of integer type + if !isInteger(x.typ) { + check.errorf(&x, invalidArg+"index %s must be integer", &x) + return + } + + if x.mode != constant_ { + return x.typ, -1 + } + + // a constant index i must be in bounds + if constant.Sign(x.val) < 0 { + check.errorf(&x, invalidArg+"index %s must not be negative", &x) + return + } + + v, valid := constant.Int64Val(constant.ToInt(x.val)) + if !valid || max >= 0 && v >= max { + if check.conf.CompilerErrorMessages { + check.errorf(&x, "array index %s out of bounds [0:%d]", x.val.String(), max) + } else { + check.errorf(&x, "index %s is out of bounds", &x) + } + return + } + + // 0 <= v [ && v < max ] + return Typ[Int], v +} + +// indexElts checks the elements (elts) of an array or slice composite literal +// against the literal's element type (typ), and the element indices against +// the literal length if known (length >= 0). It returns the length of the +// literal (maximum index value + 1). +func (check *Checker) indexedElts(elts []syntax.Expr, typ Type, length int64) int64 { + visited := make(map[int64]bool, len(elts)) + var index, max int64 + for _, e := range elts { + // determine and check index + validIndex := false + eval := e + if kv, _ := e.(*syntax.KeyValueExpr); kv != nil { + if typ, i := check.index(kv.Key, length); typ != Typ[Invalid] { + if i >= 0 { + index = i + validIndex = true + } else { + check.errorf(e, "index %s must be integer constant", kv.Key) + } + } + eval = kv.Value + } else if length >= 0 && index >= length { + check.errorf(e, "index %d is out of bounds (>= %d)", index, length) + } else { + validIndex = true + } + + // if we have a valid index, check for duplicate entries + if validIndex { + if visited[index] { + check.errorf(e, "duplicate index %d in array or slice literal", index) + } + visited[index] = true + } + index++ + if index > max { + max = index + } + + // check element against composite literal element type + var x operand + check.exprWithHint(&x, eval, typ) + check.assignment(&x, typ, "array or slice literal") + } + return max +}