package semantic // This file contains various functions that allow querying of the AST // for static analysis. // FallsThrough returns true if any execution path can fall through from the // Block statement into the parent block. It assumes any guard statement // contained in that block are semantically correct and don't fall through. func FallsThrough(block *Block) bool { // Currently, the only way *not* to fall through is to return from the // block (no break/continue/panic/infinite loop/NoReturn fn yet). // NOTE: for now the scope is unnecessary, but for (possibly labeled) // break/continue it will be required to lookup where the execution // resumes (i.e. it has to be outside the guard's containing scope). b := blockFallthrough{true} Walk(&b, block) return b.doesFallthrough } type blockFallthrough struct { doesFallthrough bool } func (b *blockFallthrough) Visit(n Node) Visitor { if n == nil { return nil } switch n := n.(type) { case *If: // if it doesn't have an else clause, ignore it, even if it doesn't // fall through we learn nothing about the block in general. if n.Else == nil { return nil } // otherwise, if both the if and else (and all else if in between) do // return, then the branch doesn't fall through. bb1 := blockFallthrough{true} bb2 := blockFallthrough{true} Walk(&bb1, n.Body) Walk(&bb2, n.Else) if !bb1.doesFallthrough && !bb2.doesFallthrough { // both the if and the else return, so the if as a whole doesn't fall // through (this works recursively, if one if-elseif then has an if // without else in the elseif, it will return that it falls through). b.doesFallthrough = false } case *Guard: // assume that the guard's else clause doesn't return - it cannot by // the semantics of the language, and from the typecheck point of view, // all guard statements will be verified, so if one of them do fall through, // an error will be reported. return nil case *Return: b.doesFallthrough = false case *Block: bb := blockFallthrough{true} for _, stmt := range n.Stmts { Walk(&bb, stmt) if !bb.doesFallthrough { // rest of the statements won't be executed b.doesFallthrough = false return nil } } } return nil }