Commit ae8da3a2 authored by Robert Griesemer's avatar Robert Griesemer

go/types: len(((*T)(nil)).X) is const if X is an array

Fixes #4744.

R=adonovan
CC=golang-dev
https://golang.org/cl/7305080
parent d8e3b16f
......@@ -181,7 +181,7 @@ var tests = []string{
// "runtime",
"runtime/cgo",
// "runtime/debug", // rejects a valid constant - issue 4744
"runtime/debug",
"runtime/pprof",
"sort",
......
......@@ -72,7 +72,7 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota
case _Cap, _Len:
mode := invalid
var val interface{}
switch typ := implicitDeref(underlying(x.typ)).(type) {
switch typ := implicitArrayDeref(underlying(x.typ)).(type) {
case *Basic:
if isString(typ) && id == _Len {
if x.mode == constant {
......@@ -85,7 +85,11 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota
case *Array:
mode = value
if !containsCallsOrReceives(arg0) {
// spec: "The expressions len(s) and cap(s) are constants
// if the type of s is an array or pointer to an array and
// the expression s does not contain channel receives or
// function calls; in this case s is not evaluated."
if !check.containsCallsOrReceives(arg0) {
mode = constant
val = typ.Len
}
......@@ -382,10 +386,10 @@ Error:
x.expr = call
}
// implicitDeref returns A if typ is of the form *A and A is an array;
// implicitArrayDeref returns A if typ is of the form *A and A is an array;
// otherwise it returns typ.
//
func implicitDeref(typ Type) Type {
func implicitArrayDeref(typ Type) Type {
if p, ok := typ.(*Pointer); ok {
if a, ok := underlying(p.Base).(*Array); ok {
return a
......@@ -394,25 +398,25 @@ func implicitDeref(typ Type) Type {
return typ
}
// containsCallsOrReceives returns true if the expression x contains
// function calls or channel receives; it returns false otherwise.
// containsCallsOrReceives reports if x contains function calls or channel receives.
// Expects that x was type-checked already.
//
func containsCallsOrReceives(x ast.Expr) bool {
res := false
func (check *checker) containsCallsOrReceives(x ast.Expr) (found bool) {
ast.Inspect(x, func(x ast.Node) bool {
switch x := x.(type) {
case *ast.CallExpr:
res = true
return false
// calls and conversions look the same
if !check.conversions[x] {
found = true
}
case *ast.UnaryExpr:
if x.Op == token.ARROW {
res = true
return false
found = true
}
}
return true
return !found // no need to continue if found
})
return res
return
}
// unparen removes any parentheses surrounding an expression and returns
......
......@@ -21,15 +21,16 @@ type checker struct {
files []*ast.File
// lazily initialized
pkg *Package // current package
firsterr error // first error encountered
idents map[*ast.Ident]Object // maps identifiers to their unique object
objects map[*ast.Object]Object // maps *ast.Objects to their unique object
initspecs map[*ast.ValueSpec]*ast.ValueSpec // "inherited" type and initialization expressions for constant declarations
methods map[*TypeName]*Scope // maps type names to associated methods
funclist []function // list of functions/methods with correct signatures and non-empty bodies
funcsig *Signature // signature of currently typechecked function
pos []token.Pos // stack of expr positions; debugging support, used if trace is set
pkg *Package // current package
firsterr error // first error encountered
idents map[*ast.Ident]Object // maps identifiers to their unique object
objects map[*ast.Object]Object // maps *ast.Objects to their unique object
initspecs map[*ast.ValueSpec]*ast.ValueSpec // "inherited" type and initialization expressions for constant declarations
methods map[*TypeName]*Scope // maps type names to associated methods
conversions map[*ast.CallExpr]bool // set of type-checked conversions (to distinguish from calls)
funclist []function // list of functions/methods with correct signatures and non-empty bodies
funcsig *Signature // signature of currently typechecked function
pos []token.Pos // stack of expr positions; debugging support, used if trace is set
}
func (check *checker) register(id *ast.Ident, obj Object) {
......@@ -392,13 +393,14 @@ type bailout struct{}
func check(ctxt *Context, fset *token.FileSet, files []*ast.File) (pkg *Package, err error) {
// initialize checker
check := checker{
ctxt: ctxt,
fset: fset,
files: files,
idents: make(map[*ast.Ident]Object),
objects: make(map[*ast.Object]Object),
initspecs: make(map[*ast.ValueSpec]*ast.ValueSpec),
methods: make(map[*TypeName]*Scope),
ctxt: ctxt,
fset: fset,
files: files,
idents: make(map[*ast.Ident]Object),
objects: make(map[*ast.Object]Object),
initspecs: make(map[*ast.ValueSpec]*ast.ValueSpec),
methods: make(map[*TypeName]*Scope),
conversions: make(map[*ast.CallExpr]bool),
}
// handle panics
......
......@@ -40,6 +40,7 @@ func (check *checker) conversion(x *operand, conv *ast.CallExpr, typ Type, iota
x.mode = value
}
check.conversions[conv] = true // for cap/len checking
x.expr = conv
x.typ = typ
return
......
......@@ -33,6 +33,10 @@ func _cap() {
assert(_4 == 20)
_5 := cap(c)
cap /* ERROR "not used" */ (c)
// issue 4744
type T struct{ a [10]int }
const _ = cap(((*T)(nil)).a)
}
func _close() {
......@@ -151,6 +155,10 @@ func _len() {
var ch <-chan int
const nn = len /* ERROR "not constant" */ (hash[<-ch][len(t)])
_ = nn // TODO(gri) remove this once unused constants get type-checked
// issue 4744
type T struct{ a [10]int }
const _ = len(((*T)(nil)).a)
}
func _make() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment