Record and validate labeled statements and jumps

This commit is contained in:
joeduffy 2017-01-23 07:25:05 -08:00
parent b724857ae1
commit 1e1a70345e
2 changed files with 45 additions and 7 deletions

View file

@ -27,7 +27,7 @@ func (b *binder) bindFunctionBody(node ast.Function) {
body := node.GetBody()
if body != nil {
v := &astBinder{b, node}
v := newASTBinder(b, node)
ast.Walk(v, body)
}
}
@ -36,8 +36,17 @@ func (b *binder) bindFunctionBody(node ast.Function) {
// does not visit children, however, as it relies on the depth-first order walk supplied by the AST package. The
// overall purpose of this is to perform validation, and record types and symbols that're needed during evaluation.
type astBinder struct {
b *binder
fnc ast.Function
b *binder
fnc ast.Function // the current function.
labels map[tokens.Name]ast.Statement // a map of known labels (for validation of jumps).
}
func newASTBinder(b *binder, fnc ast.Function) ast.Visitor {
return &astBinder{
b: b,
fnc: fnc,
labels: make(map[tokens.Name]ast.Statement),
}
}
var _ ast.Visitor = (*astBinder)(nil)
@ -53,7 +62,12 @@ func (a *astBinder) Visit(node ast.Node) ast.Visitor {
a.b.registerVariableType(n)
a.b.scope.MustRegister(symbols.NewLocalVariableSym(n))
case *ast.LabeledStatement:
// TODO: register the label somehow.
// Ensure this label doesn't already exist and then register it.
label := tokens.Name(n.Label.Ident)
if other, has := a.labels[label]; has {
a.b.Diag().Errorf(errors.ErrorDuplicateLabel.At(n), label, other)
}
a.labels[label] = n
}
// Return the current visitor to keep on visitin'.
@ -66,8 +80,22 @@ func (a *astBinder) After(node ast.Node) {
case *ast.Block:
// Exiting a block restores the prior lexical context.
a.b.scope.Pop()
case *ast.BreakStatement, *ast.ContinueStatement:
// TODO: check that the label is known.
case *ast.BreakStatement:
// If the break specifies a label, ensure that it exists.
if n.Label != nil {
label := tokens.Name(n.Label.Ident)
if _, has := a.labels[label]; !has {
a.b.Diag().Errorf(errors.ErrorUnknownJumpLabel.At(n), label, "break")
}
}
case *ast.ContinueStatement:
// If the continue specifies a label, ensure that it exists.
if n.Label != nil {
label := tokens.Name(n.Label.Ident)
if _, has := a.labels[label]; !has {
a.b.Diag().Errorf(errors.ErrorUnknownJumpLabel.At(n), label, "continue")
}
}
case *ast.IfStatement:
// Ensure that the condition is a boolean expression.
a.checkExprType(n.Condition, types.Bool)

View file

@ -133,6 +133,16 @@ var ErrorExpectedReturnExpr = &diag.Diag{
}
var ErrorUnexpectedReturnExpr = &diag.Diag{
ID: 522,
ID: 523,
Message: "Unexpected return expression; function has no return type (void)",
}
var ErrorDuplicateLabel = &diag.Diag{
ID: 524,
Message: "Duplicate label '%v': %v",
}
var ErrorUnknownJumpLabel = &diag.Diag{
ID: 525,
Message: "Unknown label '%v' used in the %v statement",
}