1
0
mirror of https://github.com/golang/go synced 2024-11-26 01:07:57 -07:00

go/types, types2: typecheck cases even if switch expression is invalid

Rather than returning right away when the switch expression is invalid,
continue type checking the type switch case.

The code was already written to be able to deal with an invalid switch
expression but it returned early nevertheless. Remove the early return
and rewrite the switch expression test slightly to better control the
scope of the x operand, leading to cleaner code.

In the process replace a tricky use of the x operand with a use of the
sx operand (plus guard, since sx may be nil if invalid).

Fixes #67962.

Change-Id: I1dc08d10078753c68449637622beb4018ed23803
Reviewed-on: https://go-review.googlesource.com/c/go/+/592555
Reviewed-by: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
This commit is contained in:
Robert Griesemer 2024-06-13 09:29:22 -07:00 committed by Gopher Robot
parent dbe03e4831
commit 69e7b2bcd6
3 changed files with 67 additions and 31 deletions

View File

@ -282,7 +282,11 @@ func (check *Checker) isNil(e syntax.Expr) bool {
return false return false
} }
// If the type switch expression is invalid, x is nil. // caseTypes typechecks the type expressions of a type case, checks for duplicate types
// using the seen map, and verifies that each type is valid with respect to the type of
// the operand x in the type switch clause. If the type switch expression is invalid, x
// must be nil. The result is the type of the last type expression; it is nil if the
// expression denotes the predeclared nil.
func (check *Checker) caseTypes(x *operand, types []syntax.Expr, seen map[Type]syntax.Expr) (T Type) { func (check *Checker) caseTypes(x *operand, types []syntax.Expr, seen map[Type]syntax.Expr) (T Type) {
var dummy operand var dummy operand
L: L:
@ -739,21 +743,18 @@ func (check *Checker) typeSwitchStmt(inner stmtContext, s *syntax.SwitchStmt, gu
} }
// check rhs // check rhs
var x operand
check.expr(nil, &x, guard.X)
if x.mode == invalid {
return
}
// TODO(gri) we may want to permit type switches on type parameter values at some point
var sx *operand // switch expression against which cases are compared against; nil if invalid var sx *operand // switch expression against which cases are compared against; nil if invalid
if isTypeParam(x.typ) { {
check.errorf(&x, InvalidTypeSwitch, "cannot use type switch on type parameter value %s", &x) var x operand
} else { check.expr(nil, &x, guard.X)
if _, ok := under(x.typ).(*Interface); ok { if x.mode != invalid {
sx = &x if isTypeParam(x.typ) {
} else { check.errorf(&x, InvalidTypeSwitch, "cannot use type switch on type parameter value %s", &x)
check.errorf(&x, InvalidTypeSwitch, "%s is not an interface", &x) } else if IsInterface(x.typ) {
sx = &x
} else {
check.errorf(&x, InvalidTypeSwitch, "%s is not an interface", &x)
}
} }
} }
@ -782,7 +783,10 @@ func (check *Checker) typeSwitchStmt(inner stmtContext, s *syntax.SwitchStmt, gu
// exactly one type, the variable has that type; otherwise, the variable // exactly one type, the variable has that type; otherwise, the variable
// has the type of the expression in the TypeSwitchGuard." // has the type of the expression in the TypeSwitchGuard."
if len(cases) != 1 || T == nil { if len(cases) != 1 || T == nil {
T = x.typ T = Typ[Invalid]
if sx != nil {
T = sx.typ
}
} }
obj := NewVar(lhs.Pos(), check.pkg, lhs.Value, T) obj := NewVar(lhs.Pos(), check.pkg, lhs.Value, T)
// TODO(mdempsky): Just use clause.Colon? Why did I even suggest // TODO(mdempsky): Just use clause.Colon? Why did I even suggest

View File

@ -279,7 +279,11 @@ func (check *Checker) isNil(e ast.Expr) bool {
return false return false
} }
// If the type switch expression is invalid, x is nil. // caseTypes typechecks the type expressions of a type case, checks for duplicate types
// using the seen map, and verifies that each type is valid with respect to the type of
// the operand x in the type switch clause. If the type switch expression is invalid, x
// must be nil. The result is the type of the last type expression; it is nil if the
// expression denotes the predeclared nil.
func (check *Checker) caseTypes(x *operand, types []ast.Expr, seen map[Type]ast.Expr) (T Type) { func (check *Checker) caseTypes(x *operand, types []ast.Expr, seen map[Type]ast.Expr) (T Type) {
var dummy operand var dummy operand
L: L:
@ -687,20 +691,19 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
check.error(s, InvalidSyntaxTree, "incorrect form of type switch guard") check.error(s, InvalidSyntaxTree, "incorrect form of type switch guard")
return return
} }
var x operand
check.expr(nil, &x, expr.X)
if x.mode == invalid {
return
}
// TODO(gri) we may want to permit type switches on type parameter values at some point
var sx *operand // switch expression against which cases are compared against; nil if invalid var sx *operand // switch expression against which cases are compared against; nil if invalid
if isTypeParam(x.typ) { {
check.errorf(&x, InvalidTypeSwitch, "cannot use type switch on type parameter value %s", &x) var x operand
} else { check.expr(nil, &x, expr.X)
if _, ok := under(x.typ).(*Interface); ok { if x.mode != invalid {
sx = &x if isTypeParam(x.typ) {
} else { check.errorf(&x, InvalidTypeSwitch, "cannot use type switch on type parameter value %s", &x)
check.errorf(&x, InvalidTypeSwitch, "%s is not an interface", &x) } else if IsInterface(x.typ) {
sx = &x
} else {
check.errorf(&x, InvalidTypeSwitch, "%s is not an interface", &x)
}
} }
} }
@ -725,7 +728,10 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
// exactly one type, the variable has that type; otherwise, the variable // exactly one type, the variable has that type; otherwise, the variable
// has the type of the expression in the TypeSwitchGuard." // has the type of the expression in the TypeSwitchGuard."
if len(clause.List) != 1 || T == nil { if len(clause.List) != 1 || T == nil {
T = x.typ T = Typ[Invalid]
if sx != nil {
T = sx.typ
}
} }
obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, T) obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, T)
scopePos := clause.Pos() + token.Pos(len("default")) // for default clause (len(List) == 0) scopePos := clause.Pos() + token.Pos(len("default")) // for default clause (len(List) == 0)

View File

@ -0,0 +1,26 @@
// Copyright 2024 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
// No `"fmt" imported and not used` error below.
// The switch cases must be typechecked even
// though the switch expression is invalid.
import "fmt"
func _() {
x := 1
for e := range x.m /* ERROR "x.m undefined (type int has no field or method m)" */ () {
switch e.(type) {
case int:
fmt.Println()
}
}
switch t := x /* ERROR "not an interface" */ .(type) {
case int, string:
_ = t
}
}