package types
import (
.
)
func ( *Checker) ( *declInfo, string, *Signature, *ast.BlockStmt, constant.Value) {
if .conf.IgnoreFuncBodies {
panic("function body not ignored")
}
if .conf._Trace {
.trace(.Pos(), "-- %s: %s", , )
}
defer func( environment, int) {
.environment =
.indent =
}(.environment, .indent)
.environment = environment{
decl: ,
scope: .scope,
version: .version,
iota: ,
sig: ,
}
.indent = 0
.stmtList(0, .List)
if .hasLabel {
.labels()
}
if .results.Len() > 0 && !.isTerminating(, "") {
.error(atPos(.Rbrace), MissingReturn, "missing return")
}
.usage(.scope)
}
func ( *Checker) ( *Scope) {
var []*Var
for , := range .elems {
= resolve(, )
if , := .(*Var); != nil && !.isParam && !.usedVars[] {
= append(, )
}
}
slices.SortFunc(, func(, *Var) int {
return cmpPos(.pos, .pos)
})
for , := range {
.softErrorf(, UnusedVar, "declared and not used: %s", .name)
}
for , := range .children {
if !.isFunc {
.()
}
}
}
type stmtContext uint
const (
breakOk stmtContext = 1 << iota
continueOk
fallthroughOk
finalSwitchCase
inTypeSwitch
)
func ( *Checker) ( ast.Stmt) {
if != nil {
.stmt(0, )
}
}
func ( []ast.Stmt) []ast.Stmt {
for := len(); > 0; -- {
if , := [-1].(*ast.EmptyStmt); ! {
return [:]
}
}
return nil
}
func ( *Checker) ( stmtContext, []ast.Stmt) {
:= &fallthroughOk != 0
:= &^ fallthroughOk
= trimTrailingEmptyStmts()
for , := range {
:=
if && +1 == len() {
|= fallthroughOk
}
.stmt(, )
}
}
func ( *Checker) ( []ast.Stmt) {
var ast.Stmt
for , := range {
var ast.Stmt
switch c := .(type) {
case *ast.CaseClause:
if len(.List) == 0 {
=
}
case *ast.CommClause:
if .Comm == nil {
=
}
default:
.error(, InvalidSyntaxTree, "case/communication clause expected")
}
if != nil {
if != nil {
.errorf(, DuplicateDefault, "multiple defaults (first at %s)", .fset.Position(.Pos()))
} else {
=
}
}
}
}
func ( *Checker) ( ast.Node, string) {
:= NewScope(.scope, .Pos(), .End(), )
.recordScope(, )
.scope =
}
func ( *Checker) () {
.scope = .scope.Parent()
}
func ( token.Token) token.Token {
if token.ADD_ASSIGN <= && <= token.AND_NOT_ASSIGN {
return + (token.ADD - token.ADD_ASSIGN)
}
return token.ILLEGAL
}
func ( *Checker) ( string, *ast.CallExpr) {
var operand
var string
var Code
switch .rawExpr(nil, &, , nil, false) {
case conversion:
= "requires function call, not conversion"
= InvalidDefer
if == "go" {
= InvalidGo
}
case expression:
= "discards result of"
= UnusedResults
case statement:
return
default:
panic("unreachable")
}
.errorf(&, , "%s %s %s", , , &)
}
func ( constant.Value) any {
if == nil {
return nil
}
switch .Kind() {
case constant.Int:
if , := constant.Int64Val(); {
return
}
if , := constant.Uint64Val(); {
return
}
case constant.Float:
if , := constant.Float64Val(); {
return
}
case constant.String:
return constant.StringVal()
}
return nil
}
type (
valueMap map[any][]valueType
valueType struct {
pos token.Pos
typ Type
}
)
func ( *Checker) ( *operand, []ast.Expr, valueMap) {
:
for , := range {
var operand
.expr(nil, &, )
if .mode == invalid || .mode == invalid {
continue
}
.convertUntyped(&, .typ)
if .mode == invalid {
continue
}
:=
.comparison(&, , token.EQL, true)
if .mode == invalid {
continue
}
if .mode != constant_ {
continue
}
if := goVal(.val); != nil {
for , := range [] {
if Identical(.typ, .typ) {
:= .newError(DuplicateCase)
.addf(&, "duplicate case %s in expression switch", &)
.addf(atPos(.pos), "previous case")
.report()
continue
}
}
[] = append([], valueType{.Pos(), .typ})
}
}
}
func ( *Checker) ( ast.Expr) bool {
if , := ast.Unparen().(*ast.Ident); != nil {
, := .lookup(.Name).(*Nil)
return
}
return false
}
func ( *Checker) ( *operand, []ast.Expr, map[Type]ast.Expr) Type {
var Type
var operand
:
for , := range {
if .isNil() {
= nil
.expr(nil, &, )
} else {
= .varType()
if !isValid() {
continue
}
}
for , := range {
if == nil && == nil || != nil && != nil && Identical(, ) {
:= "nil"
if != nil {
= TypeString(, .qualifier)
}
:= .newError(DuplicateCase)
.addf(, "duplicate case %s in type switch", )
.addf(, "previous case")
.report()
continue
}
}
[] =
if != nil && != nil {
.typeAssertion(, , , true)
}
}
if len() != 1 || == nil {
= Typ[Invalid]
if != nil {
= .typ
}
}
assert( != nil)
return
}
func ( *Checker) ( *operand, *Interface, []ast.Expr, map[string]ast.Expr) Type {
var Type
var operand
:
for , := range {
var string
if .isNil() {
.expr(nil, &, )
= nil
= "<nil>"
} else {
= .varType()
if !isValid() {
continue
}
panic("enable typeHash(T, nil)")
}
if := []; != nil {
:= "nil"
if != nil {
= TypeString(, .qualifier)
}
:= .newError(DuplicateCase)
.addf(, "duplicate case %s in type switch", )
.addf(, "previous case")
.report()
continue
}
[] =
if != nil {
.typeAssertion(, , , true)
}
}
if len() != 1 || == nil {
= Typ[Invalid]
if != nil {
= .typ
}
}
assert( != nil)
return
}
func ( *Checker) ( stmtContext, ast.Stmt) {
if debug {
defer func( *Scope) {
if := recover(); != nil {
panic()
}
assert( == .scope)
}(.scope)
}
defer .processDelayed(len(.delayed))
:= &^ (fallthroughOk | finalSwitchCase | inTypeSwitch)
switch s := .(type) {
case *ast.BadStmt, *ast.EmptyStmt:
case *ast.DeclStmt:
.declStmt(.Decl)
case *ast.LabeledStmt:
.hasLabel = true
.(, .Stmt)
case *ast.ExprStmt:
var operand
:= .rawExpr(nil, &, .X, nil, false)
var string
var Code
switch .mode {
default:
if == statement {
return
}
= "is not used"
= UnusedExpr
case builtin:
= "must be called"
= UncalledBuiltin
case typexpr:
= "is not an expression"
= NotAnExpr
}
.errorf(&, , "%s %s", &, )
case *ast.SendStmt:
var , operand
.expr(nil, &, .Chan)
.expr(nil, &, .Value)
if .mode == invalid || .mode == invalid {
return
}
:= coreType(.typ)
if == nil {
.errorf(inNode(, .Arrow), InvalidSend, invalidOp+"cannot send to %s: no core type", &)
return
}
, := .(*Chan)
if == nil {
.errorf(inNode(, .Arrow), InvalidSend, invalidOp+"cannot send to non-channel %s", &)
return
}
if .dir == RecvOnly {
.errorf(inNode(, .Arrow), InvalidSend, invalidOp+"cannot send to receive-only channel %s", &)
return
}
.assignment(&, .elem, "send")
case *ast.IncDecStmt:
var token.Token
switch .Tok {
case token.INC:
= token.ADD
case token.DEC:
= token.SUB
default:
.errorf(inNode(, .TokPos), InvalidSyntaxTree, "unknown inc/dec operation %s", .Tok)
return
}
var operand
.expr(nil, &, .X)
if .mode == invalid {
return
}
if !allNumeric(.typ) {
.errorf(.X, NonNumericIncDec, invalidOp+"%s%s (non-numeric type %s)", .X, .Tok, .typ)
return
}
:= &ast.BasicLit{ValuePos: .X.Pos(), Kind: token.INT, Value: "1"}
.binary(&, nil, .X, , , .TokPos)
if .mode == invalid {
return
}
.assignVar(.X, nil, &, "assignment")
case *ast.AssignStmt:
switch .Tok {
case token.ASSIGN, token.DEFINE:
if len(.Lhs) == 0 {
.error(, InvalidSyntaxTree, "missing lhs in assignment")
return
}
if .Tok == token.DEFINE {
.shortVarDecl(inNode(, .TokPos), .Lhs, .Rhs)
} else {
.assignVars(.Lhs, .Rhs)
}
default:
if len(.Lhs) != 1 || len(.Rhs) != 1 {
.errorf(inNode(, .TokPos), MultiValAssignOp, "assignment operation %s requires single-valued expressions", .Tok)
return
}
:= assignOp(.Tok)
if == token.ILLEGAL {
.errorf(atPos(.TokPos), InvalidSyntaxTree, "unknown assignment operation %s", .Tok)
return
}
var operand
.binary(&, nil, .Lhs[0], .Rhs[0], , .TokPos)
if .mode == invalid {
return
}
.assignVar(.Lhs[0], nil, &, "assignment")
}
case *ast.GoStmt:
.suspendedCall("go", .Call)
case *ast.DeferStmt:
.suspendedCall("defer", .Call)
case *ast.ReturnStmt:
:= .sig.results
if len(.Results) == 0 && .Len() > 0 && .vars[0].name != "" {
for , := range .vars {
if := .lookup(.name); != nil && != {
:= .newError(OutOfScopeResult)
.addf(, "result parameter %s not in scope at return", .name)
.addf(, "inner declaration of %s", )
.report()
}
}
} else {
var []*Var
if .Len() > 0 {
= .vars
}
.initVars(, .Results, )
}
case *ast.BranchStmt:
if .Label != nil {
.hasLabel = true
return
}
switch .Tok {
case token.BREAK:
if &breakOk == 0 {
.error(, MisplacedBreak, "break not in for, switch, or select statement")
}
case token.CONTINUE:
if &continueOk == 0 {
.error(, MisplacedContinue, "continue not in for statement")
}
case token.FALLTHROUGH:
if &fallthroughOk == 0 {
var string
switch {
case &finalSwitchCase != 0:
= "cannot fallthrough final case in switch"
case &inTypeSwitch != 0:
= "cannot fallthrough in type switch"
default:
= "fallthrough statement out of place"
}
.error(, MisplacedFallthrough, )
}
default:
.errorf(, InvalidSyntaxTree, "branch statement: %s", .Tok)
}
case *ast.BlockStmt:
.openScope(, "block")
defer .closeScope()
.stmtList(, .List)
case *ast.IfStmt:
.openScope(, "if")
defer .closeScope()
.simpleStmt(.Init)
var operand
.expr(nil, &, .Cond)
if .mode != invalid && !allBoolean(.typ) {
.error(.Cond, InvalidCond, "non-boolean condition in if statement")
}
.(, .Body)
switch .Else.(type) {
case nil, *ast.BadStmt:
case *ast.IfStmt, *ast.BlockStmt:
.(, .Else)
default:
.error(.Else, InvalidSyntaxTree, "invalid else branch in if statement")
}
case *ast.SwitchStmt:
|= breakOk
.openScope(, "switch")
defer .closeScope()
.simpleStmt(.Init)
var operand
if .Tag != nil {
.expr(nil, &, .Tag)
.assignment(&, nil, "switch expression")
if .mode != invalid && !Comparable(.typ) && !hasNil(.typ) {
.errorf(&, InvalidExprSwitch, "cannot switch on %s (%s is not comparable)", &, .typ)
.mode = invalid
}
} else {
.mode = constant_
.typ = Typ[Bool]
.val = constant.MakeBool(true)
.expr = &ast.Ident{NamePos: .Body.Lbrace, Name: "true"}
}
.multipleDefaults(.Body.List)
:= make(valueMap)
for , := range .Body.List {
, := .(*ast.CaseClause)
if == nil {
.error(, InvalidSyntaxTree, "incorrect expression switch case")
continue
}
.caseValues(&, .List, )
.openScope(, "case")
:=
if +1 < len(.Body.List) {
|= fallthroughOk
} else {
|= finalSwitchCase
}
.stmtList(, .Body)
.closeScope()
}
case *ast.TypeSwitchStmt:
|= breakOk | inTypeSwitch
.openScope(, "type switch")
defer .closeScope()
.simpleStmt(.Init)
var *ast.Ident
var ast.Expr
switch guard := .Assign.(type) {
case *ast.ExprStmt:
= .X
case *ast.AssignStmt:
if len(.Lhs) != 1 || .Tok != token.DEFINE || len(.Rhs) != 1 {
.error(, InvalidSyntaxTree, "incorrect form of type switch guard")
return
}
, _ = .Lhs[0].(*ast.Ident)
if == nil {
.error(, InvalidSyntaxTree, "incorrect form of type switch guard")
return
}
if .Name == "_" {
.softErrorf(, NoNewVar, "no new variable on left side of :=")
= nil
} else {
.recordDef(, nil)
}
= .Rhs[0]
default:
.error(, InvalidSyntaxTree, "incorrect form of type switch guard")
return
}
, := .(*ast.TypeAssertExpr)
if == nil || .Type != nil {
.error(, InvalidSyntaxTree, "incorrect form of type switch guard")
return
}
var *operand
{
var operand
.expr(nil, &, .X)
if .mode != invalid {
if isTypeParam(.typ) {
.errorf(&, InvalidTypeSwitch, "cannot use type switch on type parameter value %s", &)
} else if IsInterface(.typ) {
= &
} else {
.errorf(&, InvalidTypeSwitch, "%s is not an interface", &)
}
}
}
.multipleDefaults(.Body.List)
var []*Var
:= make(map[Type]ast.Expr)
for , := range .Body.List {
, := .(*ast.CaseClause)
if == nil {
.error(, InvalidSyntaxTree, "incorrect type switch case")
continue
}
:= .caseTypes(, .List, )
.openScope(, "case")
if != nil {
:= NewVar(.Pos(), .pkg, .Name, )
.declare(.scope, nil, , .Colon)
.recordImplicit(, )
= append(, )
}
.stmtList(, .Body)
.closeScope()
}
if != nil {
var bool
for , := range {
if .usedVars[] {
= true
}
.usedVars[] = true
}
if ! {
.softErrorf(, UnusedVar, "%s declared and not used", .Name)
}
}
case *ast.SelectStmt:
|= breakOk
.multipleDefaults(.Body.List)
for , := range .Body.List {
, := .(*ast.CommClause)
if == nil {
continue
}
:= false
var ast.Expr
switch s := .Comm.(type) {
case nil, *ast.SendStmt:
= true
case *ast.AssignStmt:
if len(.Rhs) == 1 {
= .Rhs[0]
}
case *ast.ExprStmt:
= .X
}
if != nil {
if , := ast.Unparen().(*ast.UnaryExpr); != nil && .Op == token.ARROW {
= true
}
}
if ! {
.error(.Comm, InvalidSelectCase, "select case must be send or receive (possibly with assignment)")
continue
}
.openScope(, "case")
if .Comm != nil {
.(, .Comm)
}
.stmtList(, .Body)
.closeScope()
}
case *ast.ForStmt:
|= breakOk | continueOk
.openScope(, "for")
defer .closeScope()
.simpleStmt(.Init)
if .Cond != nil {
var operand
.expr(nil, &, .Cond)
if .mode != invalid && !allBoolean(.typ) {
.error(.Cond, InvalidCond, "non-boolean condition in for statement")
}
}
.simpleStmt(.Post)
if , := .Post.(*ast.AssignStmt); != nil && .Tok == token.DEFINE {
.softErrorf(, InvalidPostDecl, "cannot declare in post statement")
.use(.Lhs...)
}
.(, .Body)
case *ast.RangeStmt:
|= breakOk | continueOk
.rangeStmt(, )
default:
.error(, InvalidSyntaxTree, "invalid statement")
}
}
func ( *Checker) ( stmtContext, *ast.RangeStmt) {
type = ast.Expr
type = ast.Ident
:= func( *) string { return .Name }
, := .Key, .Value
var ast.Expr = nil
:= .Tok == token.DEFINE
:= .X
:= inNode(, .TokPos)
var operand
.expr(nil, &, )
var , Type
if .mode != invalid {
, , , := rangeKeyVal(.typ, func( goVersion) bool {
return .allowVersion()
})
switch {
case ! && != "":
.softErrorf(&, InvalidRangeExpr, "cannot range over %s: %s", &, )
case !:
.softErrorf(&, InvalidRangeExpr, "cannot range over %s", &)
case == nil && != nil:
.softErrorf(, InvalidIterVar, "range over %s permits no iteration variables", &)
case == nil && != nil:
.softErrorf(, InvalidIterVar, "range over %s permits only one iteration variable", &)
case != nil:
.softErrorf(, InvalidIterVar, "range clause permits at most two iteration variables")
}
, = ,
}
.openScope(, "range")
defer .closeScope()
:= [2]{, }
:= [2]Type{, }
:= isInteger(.typ)
if {
var []*Var
for , := range {
if == nil {
continue
}
var *Var
if , := .(*); != nil {
:= ()
= NewVar(.Pos(), .pkg, , nil)
.recordDef(, )
if != "_" {
= append(, )
}
} else {
.errorf(, InvalidSyntaxTree, "cannot declare %s", )
= NewVar(.Pos(), .pkg, "_", nil)
}
assert(.typ == nil)
:= []
if == nil || == Typ[Invalid] {
.typ = Typ[Invalid]
.usedVars[] = true
continue
}
if {
assert( == 0)
.initVar(, &, "range clause")
} else {
var operand
.mode = value
.expr =
.typ =
.initVar(, &, "assignment")
}
assert(.typ != nil)
}
if len() > 0 {
:= .Body.Pos()
for , := range {
.declare(.scope, nil , , )
}
} else {
.error(, NoNewVar, "no new variables on left side of :=")
}
} else if != nil {
for , := range {
if == nil {
continue
}
:= []
if == nil || == Typ[Invalid] {
continue
}
if {
assert( == 0)
.assignVar(, nil, &, "range clause")
if .mode != invalid && !isInteger(.typ) {
.softErrorf(, InvalidRangeExpr, "cannot use iteration variable of type %s", .typ)
}
} else {
var operand
.mode = value
.expr =
.typ =
.assignVar(, nil, &, "assignment")
}
}
} else if {
.assignment(&, nil, "range clause")
}
.stmt(, .Body)
}
func ( Type, func(goVersion) bool) (, Type, string, bool) {
:= func( string) (Type, Type, string, bool) {
return Typ[Invalid], Typ[Invalid], , false
}
:=
switch typ := arrayPtrDeref(coreType()).(type) {
case nil:
return ("no core type")
case *Basic:
if isString() {
return Typ[Int], universeRune, "", true
}
if isInteger() {
if != nil && !(go1_22) {
return ("requires go1.22 or later")
}
return , nil, "", true
}
case *Array:
return Typ[Int], .elem, "", true
case *Slice:
return Typ[Int], .elem, "", true
case *Map:
return .key, .elem, "", true
case *Chan:
if .dir == SendOnly {
return ("receive from send-only channel")
}
return .elem, nil, "", true
case *Signature:
if !buildcfg.Experiment.RangeFunc && != nil && !(go1_23) {
return ("requires go1.23 or later")
}
switch {
case .Params().Len() != 1:
return ("func must be func(yield func(...) bool): wrong argument count")
case .Results().Len() != 0:
return ("func must be func(yield func(...) bool): unexpected results")
}
assert(.Recv() == nil)
, := coreType(.Params().At(0).Type()).(*Signature)
switch {
case == nil:
return ("func must be func(yield func(...) bool): argument is not func")
case .Params().Len() > 2:
return ("func must be func(yield func(...) bool): yield func has too many parameters")
case .Results().Len() != 1 || !Identical(.Results().At(0).Type(), universeBool):
if .Results().Len() == 1 && isBoolean(.Results().At(0).Type()) {
return ("func must be func(yield func(...) bool): yield func returns user-defined boolean, not bool")
} else {
return ("func must be func(yield func(...) bool): yield func does not return bool")
}
}
assert(.Recv() == nil)
if .Params().Len() >= 1 {
= .Params().At(0).Type()
}
if .Params().Len() >= 2 {
= .Params().At(1).Type()
}
return , , "", true
}
return
}