package types
import (
"go/ast"
"go/token"
. "internal/types/errors"
)
func (check *Checker ) labels (body *ast .BlockStmt ) {
all := NewScope (nil , body .Pos (), body .End (), "label" )
fwdJumps := check .blockBranches (all , nil , nil , body .List )
for _ , jmp := range fwdJumps {
var msg string
var code Code
name := jmp .Label .Name
if alt := all .Lookup (name ); alt != nil {
msg = "goto %s jumps into block"
code = JumpIntoBlock
alt .(*Label ).used = true
} else {
msg = "label %s not declared"
code = UndeclaredLabel
}
check .errorf (jmp .Label , code , msg , name )
}
for name , obj := range all .elems {
obj = resolve (name , obj )
if lbl := obj .(*Label ); !lbl .used {
check .softErrorf (lbl , UnusedLabel , "label %s declared and not used" , lbl .name )
}
}
}
type block struct {
parent *block
lstmt *ast .LabeledStmt
labels map [string ]*ast .LabeledStmt
}
func (b *block ) insert (s *ast .LabeledStmt ) {
name := s .Label .Name
if debug {
assert (b .gotoTarget (name ) == nil )
}
labels := b .labels
if labels == nil {
labels = make (map [string ]*ast .LabeledStmt )
b .labels = labels
}
labels [name ] = s
}
func (b *block ) gotoTarget (name string ) *ast .LabeledStmt {
for s := b ; s != nil ; s = s .parent {
if t := s .labels [name ]; t != nil {
return t
}
}
return nil
}
func (b *block ) enclosingTarget (name string ) *ast .LabeledStmt {
for s := b ; s != nil ; s = s .parent {
if t := s .lstmt ; t != nil && t .Label .Name == name {
return t
}
}
return nil
}
func (check *Checker ) blockBranches (all *Scope , parent *block , lstmt *ast .LabeledStmt , list []ast .Stmt ) []*ast .BranchStmt {
b := &block {parent : parent , lstmt : lstmt }
var (
varDeclPos token .Pos
fwdJumps , badJumps []*ast .BranchStmt
)
recordVarDecl := func (pos token .Pos ) {
varDeclPos = pos
badJumps = append (badJumps [:0 ], fwdJumps ...)
}
jumpsOverVarDecl := func (jmp *ast .BranchStmt ) bool {
if varDeclPos .IsValid () {
for _ , bad := range badJumps {
if jmp == bad {
return true
}
}
}
return false
}
blockBranches := func (lstmt *ast .LabeledStmt , list []ast .Stmt ) {
fwdJumps = append (fwdJumps , check .blockBranches (all , b , lstmt , list )...)
}
var stmtBranches func (ast .Stmt )
stmtBranches = func (s ast .Stmt ) {
switch s := s .(type ) {
case *ast .DeclStmt :
if d , _ := s .Decl .(*ast .GenDecl ); d != nil && d .Tok == token .VAR {
recordVarDecl (d .Pos ())
}
case *ast .LabeledStmt :
if name := s .Label .Name ; name != "_" {
lbl := NewLabel (s .Label .Pos (), check .pkg , name )
if alt := all .Insert (lbl ); alt != nil {
err := check .newError (DuplicateLabel )
err .soft = true
err .addf (lbl , "label %s already declared" , name )
err .addAltDecl (alt )
err .report ()
} else {
b .insert (s )
check .recordDef (s .Label , lbl )
}
i := 0
for _ , jmp := range fwdJumps {
if jmp .Label .Name == name {
lbl .used = true
check .recordUse (jmp .Label , lbl )
if jumpsOverVarDecl (jmp ) {
check .softErrorf (
jmp .Label ,
JumpOverDecl ,
"goto %s jumps over variable declaration at line %d" ,
name ,
check .fset .Position (varDeclPos ).Line ,
)
}
} else {
fwdJumps [i ] = jmp
i ++
}
}
fwdJumps = fwdJumps [:i ]
lstmt = s
}
stmtBranches (s .Stmt )
case *ast .BranchStmt :
if s .Label == nil {
return
}
name := s .Label .Name
switch s .Tok {
case token .BREAK :
valid := false
if t := b .enclosingTarget (name ); t != nil {
switch t .Stmt .(type ) {
case *ast .SwitchStmt , *ast .TypeSwitchStmt , *ast .SelectStmt , *ast .ForStmt , *ast .RangeStmt :
valid = true
}
}
if !valid {
check .errorf (s .Label , MisplacedLabel , "invalid break label %s" , name )
return
}
case token .CONTINUE :
valid := false
if t := b .enclosingTarget (name ); t != nil {
switch t .Stmt .(type ) {
case *ast .ForStmt , *ast .RangeStmt :
valid = true
}
}
if !valid {
check .errorf (s .Label , MisplacedLabel , "invalid continue label %s" , name )
return
}
case token .GOTO :
if b .gotoTarget (name ) == nil {
fwdJumps = append (fwdJumps , s )
return
}
default :
check .errorf (s , InvalidSyntaxTree , "branch statement: %s %s" , s .Tok , name )
return
}
obj := all .Lookup (name )
obj .(*Label ).used = true
check .recordUse (s .Label , obj )
case *ast .AssignStmt :
if s .Tok == token .DEFINE {
recordVarDecl (s .Pos ())
}
case *ast .BlockStmt :
blockBranches (lstmt , s .List )
case *ast .IfStmt :
stmtBranches (s .Body )
if s .Else != nil {
stmtBranches (s .Else )
}
case *ast .CaseClause :
blockBranches (nil , s .Body )
case *ast .SwitchStmt :
stmtBranches (s .Body )
case *ast .TypeSwitchStmt :
stmtBranches (s .Body )
case *ast .CommClause :
blockBranches (nil , s .Body )
case *ast .SelectStmt :
stmtBranches (s .Body )
case *ast .ForStmt :
stmtBranches (s .Body )
case *ast .RangeStmt :
stmtBranches (s .Body )
}
}
for _ , s := range list {
stmtBranches (s )
}
return fwdJumps
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .