This repository has been archived by the owner on Sep 2, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
tycheck.py
executable file
·171 lines (154 loc) · 5.57 KB
/
tycheck.py
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
#!/usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
import doctest
import json
import idl
class FloatFixer(object):
'''
Shift writes some doubles as ints; cheers, JavaScript.
>>> tys = idl.parse_es6_idl()
>>> import ast
>>> a = ast.load_test_ast('y5R7cnYctJv.js.dump')
>>> checker = TypeChecker(tys)
>>> checker.check(a)
Traceback (most recent call last):
...
Exception: expected float but got 0
>>> floater = FloatFixer(tys)
>>> floater.rewrite(tys.interfaces['Script'], a)
>>> checker.check(a)
'''
def __init__(self, tys):
self.tys = tys
def rewrite(self, ty, value):
if type(ty) is idl.TyInterface:
# Interfaces
self.interface(ty, value)
elif type(ty) is idl.Alt:
if type(value) is dict:
self.rewrite(self.tys.interfaces[value['type']], value)
else:
# FIXME: If floats start showing up as optional implement
# unpacking those types.
return value
elif type(ty) is idl.TyFrozenArray:
if ty.element_ty is idl.TY_DOUBLE:
# FIXME: If doubles start showing up in type alternatives, do more here
for i, v in enumerate(value):
if type(v) is int:
value[i] = float(v)
else:
for v in value:
self.rewrite(ty.element_ty, v)
return
elif ty == idl.TY_DOUBLE and type(value) is int:
assert False, 'unreachable: should have been handled at the containing site'
def interface(self, ty, node):
if ty.name != node['type']:
raise Exception(f'expected {i.name} but got {node["type"]}')
for attr in ty.attrs:
if attr.resolved_ty == idl.TY_DOUBLE and type(node[attr.name]) is int:
node[attr.name] = float(node[attr.name])
else:
self.rewrite(attr.resolved_ty, node[attr.name])
class TypeChecker(object):
'''
>>> tys = idl.parse_es6_idl()
>>> checker = TypeChecker(tys)
>>> a = {'type': 'Foo'}
>>> checker.check(a)
Traceback (most recent call last):
...
Exception: expected Script but got Foo
>>> import ast
>>> a = ast.load_test_ast('yRA0kDZHvwL.js.dump')
>>> FloatFixer(tys).rewrite(tys.interfaces['Script'], a)
>>> checker.check(a)
Now let's break the type of something; make an if statement test
a string instead of a node.
>>> a['statements'][0]['test'] = 'boogers'
>>> checker.check(a) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
Exception: no types ['interface Array...
'''
def __init__(self, tys):
self.tys = tys
def check(self, root):
'''Checks that root is a script.
If this does not throw an exception the check passed.
'''
# TODO: These could be modules, too.
ty_script = self.tys.interfaces['Script']
self.interface(ty_script, root)
def check_any(self, ty, value):
if type(ty) is idl.TyInterface:
# Interfaces
self.interface(ty, value)
return
if type(ty) is idl.TyEnum and value not in ty.values:
# Enums
raise Exception(f'{value} is not a {ty} value ({ty.values})')
if type(ty) is idl.TyNone and value is not None:
raise Exception(f'{value} should be None')
if type(ty) is idl.Alt:
for sub_ty in ty.tys:
try:
self.check_any(sub_ty, value)
return
except Exception:
# TODO: Make this a specific exception type.
if type(sub_ty) is idl.TyInterface and type(value) is dict and sub_ty.name == value.get('type'):
# Propagate this because the type field matches and there
# is probably a more specific type error buried in here.
raise
pass
raise Exception(f'no types {list(sorted(map(str, ty.tys)))} matched {json.dumps(value, indent=2)}')
if type(ty) is idl.TyFrozenArray:
if type(value) is not list:
raise Exception(f'expected list representing FrozenArray, was {value}')
for item in value:
self.check_any(ty.element_ty, item)
if ty == idl.TY_BOOLEAN and type(value) is not bool:
raise Exception(f'expected bool but got {value}')
if ty == idl.TY_DOUBLE and type(value) != float:
raise Exception(f'expected float but got {value}')
if (ty == idl.TY_LONG or
ty == idl.TY_UNSIGNED_LONG) and type(value) is not int:
raise Exception(f'expected int but got {value}')
if ty == idl.TY_STRING and type(value) is not str:
raise Exception(f'expected string but got {value}')
if ty == idl.TY_TYPE and value not in self.tys.interfaces:
raise Exception(f'unknown type {value}')
# Pass the check
def interface(self, i, node):
if i.name != node['type']:
raise Exception(f'expected {i.name} but got {node["type"]}')
# Get the names the interface expects
expected = set()
j = i
while j:
expected.update({attr.name for attr in j.attrs})
j = j.resolved_extends
# AST JSON encodes 'type' for each AST node, even ones which
# aren't polymorphic such as AssertedBlockScope.
expected.add('type')
# Get the names the node has
actual = set(node.keys())
extra = actual - expected
if extra:
raise Exception(f'unexpected properties {extra} in instance of {i.name} {node}')
missing = expected - actual
if missing:
raise Exception(f'missing properties {missing} in instance of {i.name} {node}')
j = i
while j:
for attr in j.attrs:
value = node[attr.name]
self.check_any(attr.resolved_ty, value)
j = j.resolved_extends
if __name__ == '__main__':
doctest.testmod()