1
0
mirror of https://github.com/golang/go synced 2024-11-22 14:24:45 -07:00

go/types: correct error position for inherited const init expressions

This is a port of CL 275517 from the dev.typeparams branch, to fix the
positioning of error messages for invalid const init expressions that
are inherited.

Differences from CL 275517:
 + The inherited flag is added to the constDecl intermediate
   representation.
 + The errpos override is made a positioner, the internal interface
   used by go/types to capture error position and span. For const decls
   errpos is just set to a singular point, but using positioner is
   correct and causes span start and end positions to also be
   overridden.
 + Test cases are updated to assert on just 'overflows', as the go/types
   error message is, for example, "cannot use 255 + iota (untyped int
   constant 256) as byte value in constant declaration (overflows)".
   This is more verbose than the compiler's "constant 256 overflows
   byte", but changing that is out of scope.

Fixes #42991

Change-Id: I0a71d2290f7fff5513f2a6e49b83e6f0f4da30e5
Reviewed-on: https://go-review.googlesource.com/c/go/+/276172
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Rob Findley 2020-12-08 09:44:48 -05:00 committed by Robert Findley
parent 48d6275952
commit 01b76d5fbc
5 changed files with 82 additions and 20 deletions

View File

@ -46,6 +46,7 @@ type context struct {
scope *Scope // top-most scope for lookups
pos token.Pos // if valid, identifiers are looked up as if at position pos (used by Eval)
iota constant.Value // value of iota in a constant declaration; nil otherwise
errpos positioner // if set, identifier position of a constant with inherited initializer
sig *Signature // function signature if inside a function; nil otherwise
isPanic map[*ast.CallExpr]bool // set of panic call expressions (used for termination check)
hasLabel bool // set if a function makes use of labels (only ~1% of functions); unused outside functions

View File

@ -183,7 +183,7 @@ func (check *Checker) objDecl(obj Object, def *Named) {
switch obj := obj.(type) {
case *Const:
check.decl = d // new package-level const decl
check.constDecl(obj, d.typ, d.init)
check.constDecl(obj, d.typ, d.init, d.inherited)
case *Var:
check.decl = d // new package-level var decl
check.varDecl(obj, d.lhs, d.typ, d.init)
@ -388,10 +388,11 @@ type (
importDecl struct{ spec *ast.ImportSpec }
constDecl struct {
spec *ast.ValueSpec
iota int
typ ast.Expr
init []ast.Expr
spec *ast.ValueSpec
iota int
typ ast.Expr
init []ast.Expr
inherited bool
}
varDecl struct{ spec *ast.ValueSpec }
typeDecl struct{ spec *ast.TypeSpec }
@ -424,14 +425,17 @@ func (check *Checker) walkDecl(d ast.Decl, f func(decl)) {
switch d.Tok {
case token.CONST:
// determine which initialization expressions to use
inherited := true
switch {
case s.Type != nil || len(s.Values) > 0:
last = s
inherited = false
case last == nil:
last = new(ast.ValueSpec) // make sure last exists
inherited = false
}
check.arityMatch(s, last)
f(constDecl{spec: s, iota: iota, init: last.Values, typ: last.Type})
f(constDecl{spec: s, iota: iota, typ: last.Type, init: last.Values, inherited: inherited})
case token.VAR:
check.arityMatch(s, nil)
f(varDecl{s})
@ -451,12 +455,16 @@ func (check *Checker) walkDecl(d ast.Decl, f func(decl)) {
}
}
func (check *Checker) constDecl(obj *Const, typ, init ast.Expr) {
func (check *Checker) constDecl(obj *Const, typ, init ast.Expr, inherited bool) {
assert(obj.typ == nil)
// use the correct value of iota
defer func(iota constant.Value) { check.iota = iota }(check.iota)
defer func(iota constant.Value, errpos positioner) {
check.iota = iota
check.errpos = errpos
}(check.iota, check.errpos)
check.iota = obj.val
check.errpos = nil
// provide valid constant value under all circumstances
obj.val = constant.MakeUnknown()
@ -479,6 +487,15 @@ func (check *Checker) constDecl(obj *Const, typ, init ast.Expr) {
// check initialization
var x operand
if init != nil {
if inherited {
// The initialization expression is inherited from a previous
// constant declaration, and (error) positions refer to that
// expression and not the current constant declaration. Use
// the constant identifier position for any errors during
// init expression evaluation since that is all we have
// (see issues #42991, #42992).
check.errpos = atPos(obj.pos)
}
check.expr(&x, init)
}
check.initConst(obj, &x)
@ -753,7 +770,7 @@ func (check *Checker) declStmt(d ast.Decl) {
init = d.init[i]
}
check.constDecl(obj, d.typ, init)
check.constDecl(obj, d.typ, init, d.inherited)
}
// process function literals in init expressions before scope changes

View File

@ -89,6 +89,18 @@ func (check *Checker) err(err error) {
return
}
if check.errpos != nil && isInternal {
// If we have an internal error and the errpos override is set, use it to
// augment our error positioning.
// TODO(rFindley) we may also want to augment the error message and refer
// to the position (pos) in the original expression.
span := spanOf(check.errpos)
e.Pos = span.pos
e.go116start = span.start
e.go116end = span.end
err = e
}
if check.firstErr == nil {
check.firstErr = err
}
@ -111,15 +123,15 @@ func (check *Checker) err(err error) {
}
func (check *Checker) newError(at positioner, code errorCode, soft bool, msg string) error {
ext := spanOf(at)
span := spanOf(at)
return Error{
Fset: check.fset,
Pos: ext.pos,
Pos: span.pos,
Msg: msg,
Soft: soft,
go116code: code,
go116start: ext.start,
go116end: ext.end,
go116start: span.start,
go116end: span.end,
}
}

View File

@ -17,12 +17,13 @@ import (
// A declInfo describes a package-level const, type, var, or func declaration.
type declInfo struct {
file *Scope // scope of file containing this declaration
lhs []*Var // lhs of n:1 variable declarations, or nil
typ ast.Expr // type, or nil
init ast.Expr // init/orig expression, or nil
fdecl *ast.FuncDecl // func declaration, or nil
alias bool // type alias declaration
file *Scope // scope of file containing this declaration
lhs []*Var // lhs of n:1 variable declarations, or nil
typ ast.Expr // type, or nil
init ast.Expr // init/orig expression, or nil
inherited bool // if set, the init expression is inherited from a previous constant declaration
fdecl *ast.FuncDecl // func declaration, or nil
alias bool // type alias declaration
// The deps field tracks initialization expression dependencies.
deps map[Object]bool // lazily initialized
@ -323,7 +324,7 @@ func (check *Checker) collectObjects() {
init = d.init[i]
}
d := &declInfo{file: fileScope, typ: d.typ, init: init}
d := &declInfo{file: fileScope, typ: d.typ, init: init, inherited: d.inherited}
check.declarePkgObj(name, obj, d)
}

View File

@ -107,4 +107,35 @@ func _() {
const x, y, z = 0, 1, unsafe.Sizeof(func() { _ = x /* ERROR "undeclared name" */ + y /* ERROR "undeclared name" */ + z /* ERROR "undeclared name" */ })
}
// Test cases for errors in inherited constant initialization expressions.
// Errors related to inherited initialization expressions must appear at
// the constant identifier being declared, not at the original expression
// (issues #42991, #42992).
const (
_ byte = 255 + iota
/* some gap */
_ // ERROR overflows
/* some gap */
/* some gap */ _ /* ERROR overflows */; _ /* ERROR overflows */
/* some gap */
_ = 255 + iota
_ = byte /* ERROR overflows */ (255) + iota
_ /* ERROR overflows */
)
// Test cases from issue.
const (
ok = byte(iota + 253)
bad
barn
bard // ERROR cannot convert
)
const (
c = len([1 - iota]int{})
d
e // ERROR invalid array length
f // ERROR invalid array length
)
// TODO(gri) move extra tests from testdata/const0.src into here