mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
go/types: better errors for invalid short var decls
This is a port of CL 312170 to go/types, adjusted to use go/ast and to add error codes. go/parser already emits errors for non-identifiers on the LHS of a short var decl, so a TODO is added to reconsider this redundancy. A new error code is added for repeated identifiers in short var decls. This is a bit specific, but I considered it to be a unique kind of error. The x/tools tests for this port turned up a bug: the new logic failed to call recordDef for blank identifiers. Patchset #2 contains the fix for this bug, both in go/types and cmd/compile/internal/types2. Change-Id: Ibdc40b8b4ad0e0696111d431682e1f1056fd5eeb Reviewed-on: https://go-review.googlesource.com/c/go/+/314629 Trust: Robert Findley <rfindley@google.com> Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
414af503d7
commit
6082c05d8b
7 changed files with 128 additions and 53 deletions
|
|
@ -555,6 +555,7 @@ func TestDefsInfo(t *testing.T) {
|
||||||
{`package p2; var x int`, `x`, `var p2.x int`},
|
{`package p2; var x int`, `x`, `var p2.x int`},
|
||||||
{`package p3; type x int`, `x`, `type p3.x int`},
|
{`package p3; type x int`, `x`, `type p3.x int`},
|
||||||
{`package p4; func f()`, `f`, `func p4.f()`},
|
{`package p4; func f()`, `f`, `func p4.f()`},
|
||||||
|
{`package p5; func f() int { x, _ := 1, 2; return x }`, `_`, `var _ int`},
|
||||||
|
|
||||||
// generic types must be sanitized
|
// generic types must be sanitized
|
||||||
// (need to use sufficiently nested types to provoke unexpanded types)
|
// (need to use sufficiently nested types to provoke unexpanded types)
|
||||||
|
|
|
||||||
|
|
@ -344,17 +344,15 @@ func (check *Checker) shortVarDecl(pos syntax.Pos, lhs, rhs []syntax.Expr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
name := ident.Value
|
name := ident.Value
|
||||||
if name == "_" {
|
if name != "_" {
|
||||||
continue
|
if seen[name] {
|
||||||
|
check.errorf(lhs, "%s repeated on left side of :=", lhs)
|
||||||
|
hasErr = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[name] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if seen[name] {
|
|
||||||
check.errorf(lhs, "%s repeated on left side of :=", lhs)
|
|
||||||
hasErr = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[name] = true
|
|
||||||
|
|
||||||
// Use the correct obj if the ident is redeclared. The
|
// Use the correct obj if the ident is redeclared. The
|
||||||
// variable's scope starts after the declaration; so we
|
// variable's scope starts after the declaration; so we
|
||||||
// must use Scope.Lookup here and call Scope.Insert
|
// must use Scope.Lookup here and call Scope.Insert
|
||||||
|
|
@ -374,7 +372,9 @@ func (check *Checker) shortVarDecl(pos syntax.Pos, lhs, rhs []syntax.Expr) {
|
||||||
// declare new variable
|
// declare new variable
|
||||||
obj := NewVar(ident.Pos(), check.pkg, name, nil)
|
obj := NewVar(ident.Pos(), check.pkg, name, nil)
|
||||||
lhsVars[i] = obj
|
lhsVars[i] = obj
|
||||||
newVars = append(newVars, obj)
|
if name != "_" {
|
||||||
|
newVars = append(newVars, obj)
|
||||||
|
}
|
||||||
check.recordDef(ident, obj)
|
check.recordDef(ident, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -401,6 +401,7 @@ func TestDefsInfo(t *testing.T) {
|
||||||
{`package p2; var x int`, `x`, `var p2.x int`},
|
{`package p2; var x int`, `x`, `var p2.x int`},
|
||||||
{`package p3; type x int`, `x`, `type p3.x int`},
|
{`package p3; type x int`, `x`, `type p3.x int`},
|
||||||
{`package p4; func f()`, `f`, `func p4.f()`},
|
{`package p4; func f()`, `f`, `func p4.f()`},
|
||||||
|
{`package p5; func f() int { x, _ := 1, 2; return x }`, `_`, `var _ int`},
|
||||||
|
|
||||||
// generic types must be sanitized
|
// generic types must be sanitized
|
||||||
// (need to use sufficiently nested types to provoke unexpanded types)
|
// (need to use sufficiently nested types to provoke unexpanded types)
|
||||||
|
|
|
||||||
|
|
@ -308,40 +308,60 @@ func (check *Checker) shortVarDecl(pos positioner, lhs, rhs []ast.Expr) {
|
||||||
scope := check.scope
|
scope := check.scope
|
||||||
|
|
||||||
// collect lhs variables
|
// collect lhs variables
|
||||||
var newVars []*Var
|
seen := make(map[string]bool, len(lhs))
|
||||||
var lhsVars = make([]*Var, len(lhs))
|
lhsVars := make([]*Var, len(lhs))
|
||||||
|
newVars := make([]*Var, 0, len(lhs))
|
||||||
|
hasErr := false
|
||||||
for i, lhs := range lhs {
|
for i, lhs := range lhs {
|
||||||
var obj *Var
|
ident, _ := lhs.(*ast.Ident)
|
||||||
if ident, _ := lhs.(*ast.Ident); ident != nil {
|
if ident == nil {
|
||||||
// Use the correct obj if the ident is redeclared. The
|
|
||||||
// variable's scope starts after the declaration; so we
|
|
||||||
// must use Scope.Lookup here and call Scope.Insert
|
|
||||||
// (via check.declare) later.
|
|
||||||
name := ident.Name
|
|
||||||
if alt := scope.Lookup(name); alt != nil {
|
|
||||||
// redeclared object must be a variable
|
|
||||||
if alt, _ := alt.(*Var); alt != nil {
|
|
||||||
obj = alt
|
|
||||||
} else {
|
|
||||||
check.errorf(lhs, _UnassignableOperand, "cannot assign to %s", lhs)
|
|
||||||
}
|
|
||||||
check.recordUse(ident, alt)
|
|
||||||
} else {
|
|
||||||
// declare new variable, possibly a blank (_) variable
|
|
||||||
obj = NewVar(ident.Pos(), check.pkg, name, nil)
|
|
||||||
if name != "_" {
|
|
||||||
newVars = append(newVars, obj)
|
|
||||||
}
|
|
||||||
check.recordDef(ident, obj)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
check.useLHS(lhs)
|
check.useLHS(lhs)
|
||||||
check.invalidAST(lhs, "cannot declare %s", lhs)
|
// TODO(rFindley) this is redundant with a parser error. Consider omitting?
|
||||||
|
check.errorf(lhs, _BadDecl, "non-name %s on left side of :=", lhs)
|
||||||
|
hasErr = true
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if obj == nil {
|
|
||||||
obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable
|
name := ident.Name
|
||||||
|
if name != "_" {
|
||||||
|
if seen[name] {
|
||||||
|
check.errorf(lhs, _RepeatedDecl, "%s repeated on left side of :=", lhs)
|
||||||
|
hasErr = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[name] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the correct obj if the ident is redeclared. The
|
||||||
|
// variable's scope starts after the declaration; so we
|
||||||
|
// must use Scope.Lookup here and call Scope.Insert
|
||||||
|
// (via check.declare) later.
|
||||||
|
if alt := scope.Lookup(name); alt != nil {
|
||||||
|
check.recordUse(ident, alt)
|
||||||
|
// redeclared object must be a variable
|
||||||
|
if obj, _ := alt.(*Var); obj != nil {
|
||||||
|
lhsVars[i] = obj
|
||||||
|
} else {
|
||||||
|
check.errorf(lhs, _UnassignableOperand, "cannot assign to %s", lhs)
|
||||||
|
hasErr = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// declare new variable
|
||||||
|
obj := NewVar(ident.Pos(), check.pkg, name, nil)
|
||||||
lhsVars[i] = obj
|
lhsVars[i] = obj
|
||||||
|
if name != "_" {
|
||||||
|
newVars = append(newVars, obj)
|
||||||
|
}
|
||||||
|
check.recordDef(ident, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create dummy variables where the lhs is invalid
|
||||||
|
for i, obj := range lhsVars {
|
||||||
|
if obj == nil {
|
||||||
|
lhsVars[i] = NewVar(lhs[i].Pos(), check.pkg, "_", nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
check.initVars(lhsVars, rhs, token.NoPos)
|
check.initVars(lhsVars, rhs, token.NoPos)
|
||||||
|
|
@ -349,17 +369,18 @@ func (check *Checker) shortVarDecl(pos positioner, lhs, rhs []ast.Expr) {
|
||||||
// process function literals in rhs expressions before scope changes
|
// process function literals in rhs expressions before scope changes
|
||||||
check.processDelayed(top)
|
check.processDelayed(top)
|
||||||
|
|
||||||
// declare new variables
|
if len(newVars) == 0 && !hasErr {
|
||||||
if len(newVars) > 0 {
|
|
||||||
// spec: "The scope of a constant or variable identifier declared inside
|
|
||||||
// a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl
|
|
||||||
// for short variable declarations) and ends at the end of the innermost
|
|
||||||
// containing block."
|
|
||||||
scopePos := rhs[len(rhs)-1].End()
|
|
||||||
for _, obj := range newVars {
|
|
||||||
check.declare(scope, nil, obj, scopePos) // recordObject already called
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
check.softErrorf(pos, _NoNewVar, "no new variables on left side of :=")
|
check.softErrorf(pos, _NoNewVar, "no new variables on left side of :=")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// declare new variables
|
||||||
|
// spec: "The scope of a constant or variable identifier declared inside
|
||||||
|
// a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl
|
||||||
|
// for short variable declarations) and ends at the end of the innermost
|
||||||
|
// containing block."
|
||||||
|
scopePos := rhs[len(rhs)-1].End()
|
||||||
|
for _, obj := range newVars {
|
||||||
|
check.declare(scope, nil, obj, scopePos) // id = nil: recordDef already called
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1357,6 +1357,15 @@ const (
|
||||||
// _BadDecl occurs when a declaration has invalid syntax.
|
// _BadDecl occurs when a declaration has invalid syntax.
|
||||||
_BadDecl
|
_BadDecl
|
||||||
|
|
||||||
|
// _RepeatedDecl occurs when an identifier occurs more than once on the left
|
||||||
|
// hand side of a short variable declaration.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// func _() {
|
||||||
|
// x, y, y := 1, 2, 3
|
||||||
|
// }
|
||||||
|
_RepeatedDecl
|
||||||
|
|
||||||
// _Todo is a placeholder for error codes that have not been decided.
|
// _Todo is a placeholder for error codes that have not been decided.
|
||||||
// TODO(rFindley) remove this error code after deciding on errors for generics code.
|
// TODO(rFindley) remove this error code after deciding on errors for generics code.
|
||||||
_Todo
|
_Todo
|
||||||
|
|
|
||||||
43
src/go/types/fixedbugs/issue43087.src
Normal file
43
src/go/types/fixedbugs/issue43087.src
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package p
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
a, b, b /* ERROR b repeated on left side of := */ := 1, 2, 3
|
||||||
|
_ = a
|
||||||
|
_ = b
|
||||||
|
}
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
a, _, _ := 1, 2, 3 // multiple _'s ok
|
||||||
|
_ = a
|
||||||
|
}
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
var b int
|
||||||
|
a, b, b /* ERROR b repeated on left side of := */ := 1, 2, 3
|
||||||
|
_ = a
|
||||||
|
_ = b
|
||||||
|
}
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
var a []int
|
||||||
|
a /* ERROR expected identifier */ /* ERROR non-name .* on left side of := */ [0], b := 1, 2
|
||||||
|
_ = a
|
||||||
|
_ = b
|
||||||
|
}
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
var a int
|
||||||
|
a, a /* ERROR a repeated on left side of := */ := 1, 2
|
||||||
|
_ = a
|
||||||
|
}
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
var a, b int
|
||||||
|
a, b := /* ERROR no new variables on left side of := */ 1, 2
|
||||||
|
_ = a
|
||||||
|
_ = b
|
||||||
|
}
|
||||||
6
src/go/types/testdata/stmt0.src
vendored
6
src/go/types/testdata/stmt0.src
vendored
|
|
@ -143,11 +143,11 @@ func issue6487() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func issue6766a() {
|
func issue6766a() {
|
||||||
a, a /* ERROR redeclared */ := 1, 2
|
a, a /* ERROR a repeated on left side of := */ := 1, 2
|
||||||
_ = a
|
_ = a
|
||||||
a, b, b /* ERROR redeclared */ := 1, 2, 3
|
a, b, b /* ERROR b repeated on left side of := */ := 1, 2, 3
|
||||||
_ = b
|
_ = b
|
||||||
c, c /* ERROR redeclared */, b := 1, 2, 3
|
c, c /* ERROR c repeated on left side of := */, b := 1, 2, 3
|
||||||
_ = c
|
_ = c
|
||||||
a, b := /* ERROR no new variables */ 1, 2
|
a, b := /* ERROR no new variables */ 1, 2
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue