-
Notifications
You must be signed in to change notification settings - Fork 0
/
typecheck.go
241 lines (225 loc) · 6.36 KB
/
typecheck.go
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package filter
import (
"fmt"
"github.com/bytom-gm/errors"
)
//Column describe a column
type Column struct {
Name string
Type Type
}
//Table describe a table
type Table struct {
Name string
Alias string
Columns map[string]*Column
ForeignKeys map[string]*ForeignKey
}
//ForeignKey describe a foreign key
type ForeignKey struct {
Table *Table
LocalColumn string
ForeignColumn string
}
func isType(got Type, want Type) bool {
return got == want || got == Any
}
func knownType(t Type) bool {
return t == Bool || t == String || t == Integer || t == Object
}
func valueTypes(vals []interface{}) ([]Type, error) {
valTypes := make([]Type, len(vals))
for i, val := range vals {
switch val.(type) {
case int, uint, int32, uint32, int64, uint64:
valTypes[i] = Integer
case string:
valTypes[i] = String
case bool:
valTypes[i] = Bool
default:
return nil, fmt.Errorf("unsupported value type %T", val)
}
}
return valTypes, nil
}
// typeCheck will statically type check expr with vals as the parameters
// and using tbl to determine available attributes and environments. It
// returns the inferred types of arbitrary json keys as a map.
func typeCheck(expr expr, tbl *Table, vals []interface{}) (map[string]Type, error) {
valTypes, err := valueTypes(vals)
if err != nil {
return nil, err
}
selectorTypes := make(map[string]Type)
typ, err := typeCheckExpr(expr, tbl, valTypes, selectorTypes)
if err != nil {
return nil, err
}
ok, err := assertType(expr, typ, Bool, selectorTypes)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("filter predicate must evaluate to bool, got %s", typ)
}
return selectorTypes, nil
}
func typeCheckExpr(expr expr, tbl *Table, valTypes []Type, selectorTypes map[string]Type) (typ Type, err error) {
if expr == nil { // no expr is a valid, bool type
return Bool, nil
}
switch e := expr.(type) {
case parenExpr:
return typeCheckExpr(e.inner, tbl, valTypes, selectorTypes)
case binaryExpr:
leftTyp, err := typeCheckExpr(e.l, tbl, valTypes, selectorTypes)
if err != nil {
return leftTyp, err
}
rightTyp, err := typeCheckExpr(e.r, tbl, valTypes, selectorTypes)
if err != nil {
return rightTyp, err
}
switch e.op.name {
case "OR", "AND":
ok, err := assertType(e.l, leftTyp, Bool, selectorTypes)
if err != nil {
return typ, err
}
if !ok {
return typ, fmt.Errorf("%s expects bool operands", e.op.name)
}
ok, err = assertType(e.r, rightTyp, Bool, selectorTypes)
if err != nil {
return typ, err
}
if !ok {
return typ, fmt.Errorf("%s expects bool operands", e.op.name)
}
return Bool, nil
case "=":
// The = operand requires left and right types to be equal. If
// one of our types is known but the other is not, we need to
// coerce the untyped one to a matching type.
if !knownType(leftTyp) && knownType(rightTyp) {
err := setType(e.l, rightTyp, selectorTypes)
if err != nil {
return leftTyp, err
}
leftTyp = rightTyp
}
if !knownType(rightTyp) && knownType(leftTyp) {
err := setType(e.r, leftTyp, selectorTypes)
if err != nil {
return leftTyp, err
}
rightTyp = leftTyp
}
if !isType(leftTyp, String) && !isType(leftTyp, Integer) {
return typ, fmt.Errorf("%s expects integer or string operands", e.op.name)
}
if !isType(rightTyp, String) && !isType(rightTyp, Integer) {
return typ, fmt.Errorf("%s expects integer or string operands", e.op.name)
}
if knownType(rightTyp) && knownType(leftTyp) && leftTyp != rightTyp {
return typ, fmt.Errorf("%s expects operands of matching types", e.op.name)
}
return Bool, nil
default:
panic(fmt.Errorf("unsupported operator: %s", e.op.name))
}
case placeholderExpr:
if len(valTypes) == 0 {
return Any, nil
}
if e.num <= 0 || e.num > len(valTypes) {
return typ, fmt.Errorf("unbound placeholder: $%d", e.num)
}
return valTypes[e.num-1], nil
case attrExpr:
col, ok := tbl.Columns[e.attr]
if !ok {
return typ, fmt.Errorf("invalid attribute: %s", e.attr)
}
return col.Type, nil
case valueExpr:
switch e.typ {
case tokString:
return String, nil
case tokInteger:
return Integer, nil
default:
panic(fmt.Errorf("value expr with invalid token type: %s", e.typ))
}
case selectorExpr:
typ, err = typeCheckExpr(e.objExpr, tbl, valTypes, selectorTypes)
if err != nil {
return typ, err
}
ok, err := assertType(e.objExpr, typ, Object, selectorTypes)
if err != nil {
return typ, err
}
if !ok {
return typ, errors.New("selector `.` can only be used on objects")
}
// Unfortunately, we can't know the type of the field within the
// object yet. Depending on the context, we might be able to assign it
// a type later in setType.
return Any, nil
case envExpr:
fk, ok := tbl.ForeignKeys[e.ident]
if !ok {
return typ, fmt.Errorf("invalid environment `%s`", e.ident)
}
typ, err = typeCheckExpr(e.expr, fk.Table, valTypes, selectorTypes)
if err != nil {
return typ, err
}
ok, err = assertType(e.expr, typ, Bool, selectorTypes)
if err != nil {
return typ, err
}
if !ok {
return typ, errors.New(e.ident + "(...) body must have type bool")
}
return Bool, nil
default:
panic(fmt.Errorf("unrecognized expr type %T", expr))
}
}
func assertType(expr expr, got, want Type, selectorTypes map[string]Type) (bool, error) {
if !isType(got, want) { // type does not match
return false, nil
}
if got != Any { // matching type *and* it's a concrete type
return true, nil
}
// got is `Any`. we should restrict expr to be `want`.
err := setType(expr, want, selectorTypes)
return true, err
}
func setType(expr expr, typ Type, selectorTypes map[string]Type) error {
switch e := expr.(type) {
case parenExpr:
return setType(e.inner, typ, selectorTypes)
case placeholderExpr:
// This is a special case for when we parse a txfeed filter at
// txfeed creation time. We don't have access to concrete values
// yet, so the parameters are untyped.
return nil
case selectorExpr:
/*path := strings.Join(jsonbPath(expr), ".")
boundTyp, ok := selectorTypes[path]
if ok && boundTyp != typ {
return fmt.Errorf("%q used as both %s and %s", path, boundTyp, typ)
}
selectorTypes[path] = typ*/
return nil
default:
// This should be impossible because all other expressions are
// strongly typed.
panic(fmt.Errorf("unexpected setType on %T", expr))
}
}