~mna/snow unlisted

snow/pkg/semantic/query.go -rw-r--r-- 2.2 KiB
424066c5Martin Angers doc: v0.0.5 1 year, 6 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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
}