pyscriptc 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyscriptc/__init__.py +3 -0
- pyscriptc/__main__.py +3 -0
- pyscriptc/_posthog_token.py +1 -0
- pyscriptc/builtins.py +156 -0
- pyscriptc/canonical.py +167 -0
- pyscriptc/check.py +49 -0
- pyscriptc/cli.py +271 -0
- pyscriptc/coverage.py +388 -0
- pyscriptc/debug.py +120 -0
- pyscriptc/diagnostics.py +179 -0
- pyscriptc/errors.py +56 -0
- pyscriptc/ir.py +365 -0
- pyscriptc/llvm.py +2129 -0
- pyscriptc/log.py +127 -0
- pyscriptc/lower/__init__.py +1071 -0
- pyscriptc/lower/_calls.py +1499 -0
- pyscriptc/lower/_classes.py +969 -0
- pyscriptc/lower/_core.py +301 -0
- pyscriptc/lower/_expr.py +886 -0
- pyscriptc/lower/_stdlib.py +491 -0
- pyscriptc/lower/_stdlib_shared.py +93 -0
- pyscriptc/lower/_stmt.py +1173 -0
- pyscriptc/package.py +249 -0
- pyscriptc/restlower.py +1008 -0
- pyscriptc/runtime/ps_argv.c +28 -0
- pyscriptc/runtime/ps_b64.c +168 -0
- pyscriptc/runtime/ps_coll.c +133 -0
- pyscriptc/runtime/ps_dt.c +388 -0
- pyscriptc/runtime/ps_hash.c +411 -0
- pyscriptc/runtime/ps_help.c +219 -0
- pyscriptc/runtime/ps_hmac.c +120 -0
- pyscriptc/runtime/ps_io.c +198 -0
- pyscriptc/runtime/ps_island.c +272 -0
- pyscriptc/runtime/ps_json.c +402 -0
- pyscriptc/runtime/ps_mix.c +47 -0
- pyscriptc/runtime/ps_more.c +1089 -0
- pyscriptc/runtime/ps_osmore.c +447 -0
- pyscriptc/runtime/ps_path.c +547 -0
- pyscriptc/runtime/ps_re.c +1102 -0
- pyscriptc/runtime/ps_rt.c +1175 -0
- pyscriptc/runtime/ps_rt.h +189 -0
- pyscriptc/runtime/ps_struct.c +291 -0
- pyscriptc/runtime/ps_time.c +207 -0
- pyscriptc/runtime/ps_unicode.c +1521 -0
- pyscriptc/runtime/ps_url.c +327 -0
- pyscriptc/stdlib_queue.py +191 -0
- pyscriptc/stdmore.py +1596 -0
- pyscriptc/telemetry.py +219 -0
- pyscriptc-0.1.0.dist-info/METADATA +187 -0
- pyscriptc-0.1.0.dist-info/RECORD +54 -0
- pyscriptc-0.1.0.dist-info/WHEEL +5 -0
- pyscriptc-0.1.0.dist-info/entry_points.txt +2 -0
- pyscriptc-0.1.0.dist-info/licenses/LICENSE +201 -0
- pyscriptc-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1499 @@
|
|
|
1
|
+
"""Call and intrinsic lowering for the lowerer.
|
|
2
|
+
|
|
3
|
+
Split out of `__init__.py` as E0 move 4 (calls and intrinsics land together:
|
|
4
|
+
they share the `_call_name` / `_call_attr` dispatch). `Lowerer` composes this
|
|
5
|
+
mixin in. The area owns the name-call and attribute-call dispatchers, the
|
|
6
|
+
argument fitters, the builtin container calls (`set`, `frozenset`, `bool`,
|
|
7
|
+
`list`, `dict.keys`, set methods), and the intrinsic family bodies for
|
|
8
|
+
nativeprobe, math, json, struct, hashlib, re patterns, and synthetic classes.
|
|
9
|
+
|
|
10
|
+
Module-level helpers used only here moved verbatim (`_const_set`,
|
|
11
|
+
`_const_dict_keys`, `_float_special_arg`, `_const_text`, `_capturing_groups`,
|
|
12
|
+
`_struct_arg_kinds`); the rest of the shared leaves stay in `_core.py`. The
|
|
13
|
+
boundary calls in both directions stay `self._method()` so the move is
|
|
14
|
+
behavior-identical.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import ast
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from pyscriptc.builtins import builtin_spec, method_spec
|
|
23
|
+
from pyscriptc.errors import span_of
|
|
24
|
+
from pyscriptc.ir import types_equal
|
|
25
|
+
from pyscriptc.lower._core import (
|
|
26
|
+
_assign,
|
|
27
|
+
_binary_int,
|
|
28
|
+
_compare,
|
|
29
|
+
_const_bool,
|
|
30
|
+
_const_int,
|
|
31
|
+
_const_none,
|
|
32
|
+
_dotted,
|
|
33
|
+
_name,
|
|
34
|
+
_show,
|
|
35
|
+
)
|
|
36
|
+
from pyscriptc.lower._stdlib_shared import _mod_call, _synth_module
|
|
37
|
+
from pyscriptc.restlower import builtin_call
|
|
38
|
+
from pyscriptc.restlower import method_spec as synth_method
|
|
39
|
+
from pyscriptc.stdlib_queue import MATH_FUNCTIONS
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CallsMixin:
|
|
43
|
+
def _call_name(self, node: ast.Call, span: dict[str, Any], expect: dict[str, Any] | None) -> dict[str, Any]:
|
|
44
|
+
assert isinstance(node.func, ast.Name)
|
|
45
|
+
name = node.func.id
|
|
46
|
+
if name == "super":
|
|
47
|
+
self.fail(node, "PS2002", "super is not lowered")
|
|
48
|
+
if name == "all":
|
|
49
|
+
return self._all_any(node, span, True)
|
|
50
|
+
if name == "any":
|
|
51
|
+
return self._all_any(node, span, False)
|
|
52
|
+
if name == "enumerate":
|
|
53
|
+
self.fail(node, "PS2002", "enumerate is only lowered as the iterator of a for")
|
|
54
|
+
if name == "zip":
|
|
55
|
+
self.fail(node, "PS2002", "zip is only lowered as the iterator of a for")
|
|
56
|
+
if name == "set":
|
|
57
|
+
return self._set_call(node, span, expect)
|
|
58
|
+
if name == "frozenset":
|
|
59
|
+
return self._frozenset_call(node, span, expect)
|
|
60
|
+
if name == "bool":
|
|
61
|
+
return self._bool_call(node, span)
|
|
62
|
+
if name == "list":
|
|
63
|
+
return self._list_call(node, span, expect)
|
|
64
|
+
if name == "range":
|
|
65
|
+
self.fail(node, "PS2002", "range is only lowered as the iterator of a for")
|
|
66
|
+
found = builtin_call(self, node, span)
|
|
67
|
+
if found is not None:
|
|
68
|
+
return found
|
|
69
|
+
if name in self.type_aliases:
|
|
70
|
+
self.fail(node, "PS2002", f"{name} is a typing name and is not a value")
|
|
71
|
+
if name in {"int", "float", "len", "str"}:
|
|
72
|
+
if name == "float":
|
|
73
|
+
special = _float_special_arg(node)
|
|
74
|
+
if special is not None:
|
|
75
|
+
return {"kind": "const", "value": special, "type": {"kind": "float"}, "span": span}
|
|
76
|
+
self._reject_keywords(node)
|
|
77
|
+
args = [self._expr(arg) for arg in node.args]
|
|
78
|
+
spec = builtin_spec(name, [arg["type"] for arg in args])
|
|
79
|
+
if spec is None:
|
|
80
|
+
self.fail(node, "PS2003", f"{name} is not lowered for these arguments")
|
|
81
|
+
if name == "float" and args[0]["type"]["kind"] == "int":
|
|
82
|
+
return {"kind": "cast", "op": "int_to_float", "value": args[0], "type": spec, "span": span}
|
|
83
|
+
if name == "str" and args[0]["type"]["kind"] == "str":
|
|
84
|
+
return args[0]
|
|
85
|
+
if name == "str" and args[0]["type"]["kind"] == "none":
|
|
86
|
+
# None is not a runtime value; str(None) folds at lowering.
|
|
87
|
+
return {"kind": "const", "value": "None", "type": spec, "span": span}
|
|
88
|
+
if name == "int" and args[0]["type"]["kind"] in {"bytes", "str"}:
|
|
89
|
+
return self._abi_call("builtins", "int", args, spec, "ps_int_from_bytes", span)
|
|
90
|
+
return {
|
|
91
|
+
"kind": "call",
|
|
92
|
+
"callee": {"kind": "builtin", "name": name},
|
|
93
|
+
"args": args,
|
|
94
|
+
"type": spec,
|
|
95
|
+
"span": span,
|
|
96
|
+
}
|
|
97
|
+
binding = self.bindings.get(name)
|
|
98
|
+
if binding is not None:
|
|
99
|
+
if binding.queued:
|
|
100
|
+
self._queued(node, f"{binding.module}.{binding.attr}" if binding.attr else binding.module)
|
|
101
|
+
if binding.kind == "func":
|
|
102
|
+
return self._module_call(node, span, binding.module, binding.attr, expect)
|
|
103
|
+
if binding.kind == "pyfunc":
|
|
104
|
+
return self._linked_call(node, span, self.pkg_funcs[name], name)
|
|
105
|
+
self.fail(node, "PS2002", f"{name} is not a function")
|
|
106
|
+
if name in self.class_aliases or name in self.classes:
|
|
107
|
+
return self._construct(node, span, self.class_aliases.get(name, name))
|
|
108
|
+
if name in self.sigs:
|
|
109
|
+
params, ret = self.sigs[name]
|
|
110
|
+
args = self._fill_args(node, params, name)
|
|
111
|
+
return {
|
|
112
|
+
"kind": "call",
|
|
113
|
+
"callee": {"kind": "function", "name": self._ir_name(name)},
|
|
114
|
+
"args": args,
|
|
115
|
+
"type": ret,
|
|
116
|
+
"span": span,
|
|
117
|
+
}
|
|
118
|
+
self.fail(node, "PS2002", f"call to {name} is not lowered")
|
|
119
|
+
raise AssertionError("unreachable")
|
|
120
|
+
|
|
121
|
+
def _call_attr(self, node: ast.Call, span: dict[str, Any], expect: dict[str, Any] | None) -> dict[str, Any]:
|
|
122
|
+
assert isinstance(node.func, ast.Attribute)
|
|
123
|
+
resolved = self._resolve_module(node.func)
|
|
124
|
+
if resolved is not None:
|
|
125
|
+
dotted, module, member, status = resolved
|
|
126
|
+
if status == "erased":
|
|
127
|
+
self.fail(node, "PS2002", f"{dotted} is a typing name and is not a value")
|
|
128
|
+
if status == "pkg":
|
|
129
|
+
item = self._pkg_item(node, module, member)
|
|
130
|
+
if item["kind"] == "class":
|
|
131
|
+
return self._construct(node, span, str(item["name"]))
|
|
132
|
+
if item["kind"] != "func":
|
|
133
|
+
self.fail(node, "PS2002", f"{dotted} is not a function")
|
|
134
|
+
return self._linked_call(node, span, item, dotted)
|
|
135
|
+
if status == "queued":
|
|
136
|
+
self._queued(node, dotted)
|
|
137
|
+
if status != "lowered":
|
|
138
|
+
self.fail(node, "PS2001", f"{dotted} is not in the lowered subset of {module}")
|
|
139
|
+
return self._module_call(node, span, module, member, expect)
|
|
140
|
+
named = None
|
|
141
|
+
if isinstance(node.func.value, ast.Name):
|
|
142
|
+
named = self.class_aliases.get(node.func.value.id, node.func.value.id)
|
|
143
|
+
if named is not None and named in self.classes:
|
|
144
|
+
return self._named_class_call(node, span, named)
|
|
145
|
+
receiver = self._expr(node.func.value)
|
|
146
|
+
if receiver["type"]["kind"] == "class" and receiver["type"].get("name") == "Path":
|
|
147
|
+
if node.func.attr in {"__enter__", "__exit__"}:
|
|
148
|
+
return self._path_context(node, span, receiver)
|
|
149
|
+
if receiver["type"]["kind"] == "list" and receiver["type"].get("set"):
|
|
150
|
+
return self._set_method(node, span, receiver)
|
|
151
|
+
if receiver["type"]["kind"] == "list":
|
|
152
|
+
return self._list_method(node, span, receiver)
|
|
153
|
+
kind = receiver["type"]["kind"]
|
|
154
|
+
if kind in {"str", "bytes"}:
|
|
155
|
+
attr = node.func.attr
|
|
156
|
+
if attr in {"startswith", "endswith"} and len(node.args) == 1 and isinstance(node.args[0], ast.Tuple):
|
|
157
|
+
# s.startswith((a, b)) is a or-chain of scalar calls; the empty
|
|
158
|
+
# tuple is constant False per CPython.
|
|
159
|
+
elts = node.args[0].elts
|
|
160
|
+
if not elts:
|
|
161
|
+
return {"kind": "const", "value": False, "type": {"kind": "bool"}, "span": span}
|
|
162
|
+
rewritten = ast.BoolOp(
|
|
163
|
+
op=ast.Or(),
|
|
164
|
+
values=[ast.Call(func=node.func, args=[elt], keywords=[]) for elt in elts],
|
|
165
|
+
)
|
|
166
|
+
for sub in ast.walk(rewritten):
|
|
167
|
+
sub.lineno, sub.col_offset = node.lineno, node.col_offset
|
|
168
|
+
sub.end_lineno, sub.end_col_offset = node.end_lineno, node.end_col_offset
|
|
169
|
+
return self._expr(rewritten, expect)
|
|
170
|
+
if attr in {"partition", "rpartition"}:
|
|
171
|
+
# The result is a 3-tuple of the receiver's kind; it materializes
|
|
172
|
+
# as the synthetic tuple_3 class, like zip/enumerate pairs.
|
|
173
|
+
self._reject_keywords(node)
|
|
174
|
+
if len(node.args) != 1:
|
|
175
|
+
self.fail(node, "PS2003", f"{kind}.{attr} expects 1 argument(s)")
|
|
176
|
+
sep = self._expr(node.args[0])
|
|
177
|
+
self._expect(node, sep["type"], {"kind": kind}, f"{kind}.{attr} separator")
|
|
178
|
+
typ = self._tuple_type([{"kind": kind}] * 3, node)
|
|
179
|
+
return {
|
|
180
|
+
"kind": "call",
|
|
181
|
+
"callee": {"kind": "method", "receiver": kind, "name": attr, "tuple": typ["name"]},
|
|
182
|
+
"args": [receiver, sep],
|
|
183
|
+
"type": typ,
|
|
184
|
+
"span": span,
|
|
185
|
+
}
|
|
186
|
+
self._reject_keywords(node)
|
|
187
|
+
args = [self._expr(arg) for arg in node.args]
|
|
188
|
+
if attr in {"encode", "decode"} and args:
|
|
189
|
+
enc = args[0]
|
|
190
|
+
value = str(enc.get("value", "")).lower().replace("-", "").replace("_", "")
|
|
191
|
+
if enc["kind"] != "const" or value != "utf8":
|
|
192
|
+
self.fail(node, "PS2003", f"{kind}.{attr} lowers the 'utf-8' encoding only")
|
|
193
|
+
spec = method_spec(receiver["type"], node.func.attr, [arg["type"] for arg in args])
|
|
194
|
+
if spec is None:
|
|
195
|
+
self.fail(node, "PS2001", f"call of attribute is not lowered ({_dotted(node.func)})")
|
|
196
|
+
return {
|
|
197
|
+
"kind": "call",
|
|
198
|
+
"callee": {"kind": "method", "receiver": kind, "name": node.func.attr},
|
|
199
|
+
"args": [receiver, *args],
|
|
200
|
+
"type": spec,
|
|
201
|
+
"span": span,
|
|
202
|
+
}
|
|
203
|
+
if kind == "class" and receiver["type"]["name"] == "Hash":
|
|
204
|
+
return self._hash_method(node, span, receiver)
|
|
205
|
+
if kind == "class" and receiver["type"]["name"] == "Pattern":
|
|
206
|
+
return self._pattern_method(node, span, receiver)
|
|
207
|
+
if kind == "class" and synth_method(receiver["type"]["name"], node.func.attr) is not None:
|
|
208
|
+
return self._synth_call(node, span, receiver)
|
|
209
|
+
if kind == "dict" and node.func.attr == "keys":
|
|
210
|
+
return self._dict_keys(node, span, receiver)
|
|
211
|
+
if kind == "dict" and node.func.attr == "get" and len(node.args) == 2 and not node.keywords:
|
|
212
|
+
# `d.get(k, default)` is `d[k] if k in d else default`. Only this
|
|
213
|
+
# 2-arg positional form lowers: `val | none` is not an IR type, so
|
|
214
|
+
# the 1-arg form keeps the generic attribute fence below.
|
|
215
|
+
key = self._expr(node.args[0])
|
|
216
|
+
self._expect(node, key["type"], receiver["type"]["key"], "dict.get key")
|
|
217
|
+
d_t = self._temp("getd").replace(".", "_")
|
|
218
|
+
k_t = self._temp("getk").replace(".", "_")
|
|
219
|
+
self.locals[d_t] = receiver["type"]
|
|
220
|
+
self.locals[k_t] = receiver["type"]["key"]
|
|
221
|
+
for t, v in ((d_t, receiver), (k_t, key)):
|
|
222
|
+
self.prelude.append({"kind": "assign", "target": t, "value": v, "span": span})
|
|
223
|
+
ifexp = ast.IfExp(
|
|
224
|
+
test=ast.Compare(
|
|
225
|
+
left=ast.Name(id=k_t, ctx=ast.Load()),
|
|
226
|
+
ops=[ast.In()],
|
|
227
|
+
comparators=[ast.Name(id=d_t, ctx=ast.Load())],
|
|
228
|
+
),
|
|
229
|
+
body=ast.Subscript(
|
|
230
|
+
value=ast.Name(id=d_t, ctx=ast.Load()),
|
|
231
|
+
slice=ast.Name(id=k_t, ctx=ast.Load()),
|
|
232
|
+
ctx=ast.Load(),
|
|
233
|
+
),
|
|
234
|
+
orelse=node.args[1],
|
|
235
|
+
)
|
|
236
|
+
for sub in ast.walk(ifexp):
|
|
237
|
+
sub.lineno, sub.col_offset = node.lineno, node.col_offset
|
|
238
|
+
sub.end_lineno, sub.end_col_offset = node.end_lineno, node.end_col_offset
|
|
239
|
+
value = self._expr(ifexp, receiver["type"]["value"])
|
|
240
|
+
self._expect(node, value["type"], receiver["type"]["value"], "dict.get default")
|
|
241
|
+
return value
|
|
242
|
+
if kind == "dict" and receiver["type"].get("ordered") and node.func.attr in {"move_to_end", "popitem"}:
|
|
243
|
+
self.fail(node, "PS2001", f"OrderedDict.{node.func.attr} is not lowered")
|
|
244
|
+
if kind == "dict" and node.func.attr in {"values", "items", "pop", "setdefault", "update", "clear"}:
|
|
245
|
+
attr = node.func.attr
|
|
246
|
+
self._reject_keywords(node)
|
|
247
|
+
dtyp = receiver["type"]
|
|
248
|
+
key_t, val_t = dtyp["key"], dtyp["value"]
|
|
249
|
+
if attr in {"values", "items"}:
|
|
250
|
+
if node.args:
|
|
251
|
+
self.fail(node, "PS2003", f"dict.{attr} expects 0 argument(s)")
|
|
252
|
+
if key_t["kind"] not in {"int", "str"}:
|
|
253
|
+
self.fail(node, "PS2002", "dict keys are int or str")
|
|
254
|
+
if attr == "values":
|
|
255
|
+
return self._abi_call(
|
|
256
|
+
"builtins", "dict.values", [receiver], {"kind": "list", "elem": val_t}, "ps_dict_values", span
|
|
257
|
+
)
|
|
258
|
+
elem = self._tuple_type([key_t, val_t], node)
|
|
259
|
+
return self._abi_call(
|
|
260
|
+
"builtins", "dict.items", [receiver], {"kind": "list", "elem": elem}, "ps_dict_items", span
|
|
261
|
+
)
|
|
262
|
+
if attr in {"update", "clear"}:
|
|
263
|
+
# Both mutate the map in place; the abi call carries the receiver
|
|
264
|
+
# pointer, so a name is not required.
|
|
265
|
+
if attr == "clear":
|
|
266
|
+
if node.args:
|
|
267
|
+
self.fail(node, "PS2003", "dict.clear expects 0 argument(s)")
|
|
268
|
+
call = self._abi_call("builtins", "dict.clear", [receiver], {"kind": "none"}, "ps_map_clear", span)
|
|
269
|
+
else:
|
|
270
|
+
if len(node.args) != 1:
|
|
271
|
+
self.fail(node, "PS2003", "dict.update expects 1 argument(s)")
|
|
272
|
+
other = self._expr(node.args[0])
|
|
273
|
+
if other["type"]["kind"] != "dict":
|
|
274
|
+
self.fail(node, "PS1003", "dict.update takes a dict")
|
|
275
|
+
self._expect(node, other["type"]["key"], key_t, "dict.update key")
|
|
276
|
+
self._expect(node, other["type"]["value"], val_t, "dict.update value")
|
|
277
|
+
call = self._abi_call(
|
|
278
|
+
"builtins", "dict.update", [receiver, other], {"kind": "none"}, "ps_map_update", span
|
|
279
|
+
)
|
|
280
|
+
self.prelude.append({"kind": "expr", "expr": call, "span": span})
|
|
281
|
+
return _const_none(span)
|
|
282
|
+
# pop and setdefault are statements: the dict is bound to a temp,
|
|
283
|
+
# the key checked with `in`, and the value bound before a ps_map_del
|
|
284
|
+
# or a setitem. `del` shares the ps_map_del call.
|
|
285
|
+
if attr == "setdefault" and len(node.args) != 2:
|
|
286
|
+
self.fail(node, "PS2003", "dict.setdefault expects 2 argument(s)")
|
|
287
|
+
if attr == "pop" and len(node.args) not in {1, 2}:
|
|
288
|
+
self.fail(node, "PS2003", "dict.pop expects 1 or 2 argument(s)")
|
|
289
|
+
d_t = self._temp("popd").replace(".", "_")
|
|
290
|
+
k_t = self._temp("popk").replace(".", "_")
|
|
291
|
+
key = self._expr(node.args[0])
|
|
292
|
+
self._expect(node, key["type"], key_t, f"dict.{attr} key")
|
|
293
|
+
self.locals[d_t] = dtyp
|
|
294
|
+
self.locals[k_t] = key_t
|
|
295
|
+
for t, v in ((d_t, receiver), (k_t, key)):
|
|
296
|
+
self.prelude.append({"kind": "assign", "target": t, "value": v, "span": span})
|
|
297
|
+
if attr == "setdefault":
|
|
298
|
+
val = self._expr(node.args[1], val_t)
|
|
299
|
+
self._expect(node, val["type"], val_t, "dict.setdefault value")
|
|
300
|
+
cond = {
|
|
301
|
+
"kind": "contains",
|
|
302
|
+
"hay": _name(d_t, dtyp, span),
|
|
303
|
+
"needle": _name(k_t, key_t, span),
|
|
304
|
+
"negated": True,
|
|
305
|
+
"type": {"kind": "bool"},
|
|
306
|
+
"span": span,
|
|
307
|
+
}
|
|
308
|
+
self.prelude.append(
|
|
309
|
+
{
|
|
310
|
+
"kind": "if",
|
|
311
|
+
"cond": cond,
|
|
312
|
+
"then": [
|
|
313
|
+
{
|
|
314
|
+
"kind": "setitem",
|
|
315
|
+
"container": _name(d_t, dtyp, span),
|
|
316
|
+
"index": _name(k_t, key_t, span),
|
|
317
|
+
"value": val,
|
|
318
|
+
"span": span,
|
|
319
|
+
}
|
|
320
|
+
],
|
|
321
|
+
"else": [],
|
|
322
|
+
"span": span,
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
return {
|
|
326
|
+
"kind": "subscript",
|
|
327
|
+
"value": _name(d_t, dtyp, span),
|
|
328
|
+
"index": _name(k_t, key_t, span),
|
|
329
|
+
"type": val_t,
|
|
330
|
+
"span": span,
|
|
331
|
+
}
|
|
332
|
+
v_t = self._temp("popv").replace(".", "_")
|
|
333
|
+
self.locals[v_t] = val_t
|
|
334
|
+
getitem = {
|
|
335
|
+
"kind": "subscript",
|
|
336
|
+
"value": _name(d_t, dtyp, span),
|
|
337
|
+
"index": _name(k_t, key_t, span),
|
|
338
|
+
"type": val_t,
|
|
339
|
+
"span": span,
|
|
340
|
+
}
|
|
341
|
+
delitem = self._abi_call(
|
|
342
|
+
"builtins",
|
|
343
|
+
"dict.delitem",
|
|
344
|
+
[_name(d_t, dtyp, span), _name(k_t, key_t, span)],
|
|
345
|
+
{"kind": "none"},
|
|
346
|
+
"ps_map_del",
|
|
347
|
+
span,
|
|
348
|
+
)
|
|
349
|
+
then = [
|
|
350
|
+
{"kind": "assign", "target": v_t, "value": getitem, "span": span},
|
|
351
|
+
{"kind": "expr", "expr": delitem, "span": span},
|
|
352
|
+
]
|
|
353
|
+
if len(node.args) == 2:
|
|
354
|
+
fallback = self._expr(node.args[1], val_t)
|
|
355
|
+
self._expect(node, fallback["type"], val_t, "dict.pop default")
|
|
356
|
+
other = [{"kind": "assign", "target": v_t, "value": fallback, "span": span}]
|
|
357
|
+
else:
|
|
358
|
+
other = [{"kind": "raise", "exc": "KeyError", "message": None, "span": span}]
|
|
359
|
+
cond = {
|
|
360
|
+
"kind": "contains",
|
|
361
|
+
"hay": _name(d_t, dtyp, span),
|
|
362
|
+
"needle": _name(k_t, key_t, span),
|
|
363
|
+
"negated": False,
|
|
364
|
+
"type": {"kind": "bool"},
|
|
365
|
+
"span": span,
|
|
366
|
+
}
|
|
367
|
+
self.prelude.append({"kind": "if", "cond": cond, "then": then, "else": other, "span": span})
|
|
368
|
+
return _name(v_t, val_t, span)
|
|
369
|
+
if kind == "class":
|
|
370
|
+
return self._method_call(node, span, receiver)
|
|
371
|
+
self.fail(node, "PS2001", f"call of attribute is not lowered ({_dotted(node.func)})")
|
|
372
|
+
raise AssertionError("unreachable")
|
|
373
|
+
|
|
374
|
+
def _nativeprobe_call(self, node: ast.Call, span: dict[str, Any], attr: str) -> dict[str, Any]:
|
|
375
|
+
specs = {
|
|
376
|
+
"crc32": ("ps_mix_crc32", [{"kind": "bytes"}]),
|
|
377
|
+
"crc32_island": ("ps_island_mix_crc32", [{"kind": "bytes"}]),
|
|
378
|
+
"crc32_n": ("ps_mix_crc32_n", [{"kind": "bytes"}, {"kind": "int"}]),
|
|
379
|
+
"crc32_n_island": ("ps_island_mix_crc32_n", [{"kind": "bytes"}, {"kind": "int"}]),
|
|
380
|
+
}
|
|
381
|
+
found = specs.get(attr)
|
|
382
|
+
if found is None:
|
|
383
|
+
self.fail(node, "PS2001", f"nativeprobe.{attr} is not in the lowered subset of nativeprobe")
|
|
384
|
+
symbol, params = found
|
|
385
|
+
self._reject_keywords(node)
|
|
386
|
+
if len(node.args) != len(params):
|
|
387
|
+
self.fail(node, "PS2003", f"nativeprobe.{attr} expects {len(params)} argument(s)")
|
|
388
|
+
args = []
|
|
389
|
+
for arg_node, want in zip(node.args, params, strict=True):
|
|
390
|
+
arg = self._expr(arg_node, want)
|
|
391
|
+
if not types_equal(arg["type"], want):
|
|
392
|
+
self.fail(node, "PS1003", f"nativeprobe.{attr} argument is {_show(arg['type'])}")
|
|
393
|
+
args.append(arg)
|
|
394
|
+
return self._abi_call("nativeprobe", attr, args, {"kind": "int"}, symbol, span)
|
|
395
|
+
|
|
396
|
+
def _math_call(self, node: ast.Call, span: dict[str, Any], attr: str) -> dict[str, Any]:
|
|
397
|
+
spec = MATH_FUNCTIONS.get(attr)
|
|
398
|
+
if spec is None:
|
|
399
|
+
self.fail(node, "PS2001", f"math.{attr} is not in the lowered subset of math")
|
|
400
|
+
if len(node.args) != len(spec["params"]):
|
|
401
|
+
self.fail(node, "PS2003", f"math.{attr} expects {len(spec['params'])} argument(s)")
|
|
402
|
+
args = []
|
|
403
|
+
for arg_node, want in zip(node.args, spec["params"], strict=True):
|
|
404
|
+
arg = self._expr(arg_node)
|
|
405
|
+
if want["kind"] == "float" and arg["type"]["kind"] == "int":
|
|
406
|
+
arg = {"kind": "cast", "op": "int_to_float", "value": arg, "type": {"kind": "float"}, "span": span}
|
|
407
|
+
if not types_equal(arg["type"], want):
|
|
408
|
+
self.fail(node, "PS1003", f"math.{attr} argument is {_show(arg['type'])}, expected {_show(want)}")
|
|
409
|
+
args.append(arg)
|
|
410
|
+
return _mod_call("math", attr, args, spec["return"], span)
|
|
411
|
+
|
|
412
|
+
def _fixed_call(
|
|
413
|
+
self, node: ast.Call, span: dict[str, Any], module: str, attr: str, spec: dict[str, Any]
|
|
414
|
+
) -> dict[str, Any]:
|
|
415
|
+
params: list[dict[str, Any]] = spec["params"]
|
|
416
|
+
names: list[str] | None = spec.get("names")
|
|
417
|
+
minimum = int(spec.get("min", len(params)))
|
|
418
|
+
if names:
|
|
419
|
+
named: list[dict[str, Any]] = []
|
|
420
|
+
defaults: list[str] = spec.get("defaults", [])
|
|
421
|
+
for index, want in enumerate(params):
|
|
422
|
+
param: dict[str, Any] = {"name": names[index], "type": want}
|
|
423
|
+
if index >= minimum:
|
|
424
|
+
default = defaults[index - minimum]
|
|
425
|
+
param["default"] = {"kind": "const", "value": default, "type": want, "span": span}
|
|
426
|
+
named.append(param)
|
|
427
|
+
args = self._fill_args(node, named, f"{module}.{attr}")
|
|
428
|
+
expr = _mod_call(module, attr, args, spec["return"], span)
|
|
429
|
+
if "symbol" in spec:
|
|
430
|
+
expr["symbol"] = spec["symbol"]
|
|
431
|
+
return expr
|
|
432
|
+
if node.keywords:
|
|
433
|
+
self.fail(node, "PS2003", "keyword arguments are not lowered")
|
|
434
|
+
if len(node.args) < minimum or len(node.args) > len(params):
|
|
435
|
+
self.fail(node, "PS2003", f"{module}.{attr} expects {minimum} argument(s)")
|
|
436
|
+
args: list[dict[str, Any]] = []
|
|
437
|
+
defaults: list[str] = spec.get("defaults", [])
|
|
438
|
+
for index, want in enumerate(params):
|
|
439
|
+
if index >= len(node.args):
|
|
440
|
+
default = defaults[index - minimum]
|
|
441
|
+
args.append({"kind": "const", "value": default, "type": {"kind": "str"}, "span": span})
|
|
442
|
+
continue
|
|
443
|
+
arg = self._expr(node.args[index])
|
|
444
|
+
self._expect_wave(node, arg["type"], want, f"{module}.{attr}")
|
|
445
|
+
args.append(arg)
|
|
446
|
+
expr = _mod_call(module, attr, args, spec["return"], span)
|
|
447
|
+
if "symbol" in spec:
|
|
448
|
+
expr["symbol"] = spec["symbol"]
|
|
449
|
+
return expr
|
|
450
|
+
|
|
451
|
+
def _expect_wave(self, node: ast.AST, got: dict[str, Any], want: dict[str, Any], what: str) -> None:
|
|
452
|
+
if want["kind"] == "buf":
|
|
453
|
+
if got["kind"] not in {"str", "bytes"}:
|
|
454
|
+
self.fail(node, "PS1003", f"{what} argument is {_show(got)}, expected str or bytes")
|
|
455
|
+
return
|
|
456
|
+
if not types_equal(got, want):
|
|
457
|
+
self.fail(node, "PS1003", f"{what} argument is {_show(got)}, expected {_show(want)}")
|
|
458
|
+
|
|
459
|
+
def _json_loads(self, node: ast.Call, span: dict[str, Any], expect: dict[str, Any] | None) -> dict[str, Any]:
|
|
460
|
+
if len(node.args) != 1:
|
|
461
|
+
self.fail(node, "PS2003", "json.loads expects 1 argument(s)")
|
|
462
|
+
if expect is None:
|
|
463
|
+
self.fail(node, "PS1001", "json.loads needs the result annotation")
|
|
464
|
+
self._json_type(node, expect, "json.loads")
|
|
465
|
+
arg = self._expr(node.args[0])
|
|
466
|
+
if arg["type"]["kind"] != "str":
|
|
467
|
+
self.fail(node, "PS1003", "json.loads argument is " + _show(arg["type"]) + ", expected str")
|
|
468
|
+
return _mod_call("json", "loads", [arg], expect, span)
|
|
469
|
+
|
|
470
|
+
def _json_dumps(self, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
471
|
+
if len(node.args) != 1:
|
|
472
|
+
self.fail(node, "PS2003", "json.dumps expects 1 argument(s)")
|
|
473
|
+
arg = self._expr(node.args[0])
|
|
474
|
+
self._json_type(node, arg["type"], "json.dumps")
|
|
475
|
+
return _mod_call("json", "dumps", [arg], {"kind": "str"}, span)
|
|
476
|
+
|
|
477
|
+
def _json_type(self, node: ast.AST, typ: dict[str, Any], what: str) -> None:
|
|
478
|
+
kind = typ["kind"]
|
|
479
|
+
if kind == "float":
|
|
480
|
+
self.fail(node, "PS2001", "float JSON is not lowered")
|
|
481
|
+
if kind in {"int", "bool", "str"}:
|
|
482
|
+
return
|
|
483
|
+
if kind == "list":
|
|
484
|
+
if typ["elem"]["kind"] == "bool":
|
|
485
|
+
self.fail(node, "PS2002", "list[bool] is not lowered")
|
|
486
|
+
self._json_type(node, typ["elem"], what)
|
|
487
|
+
return
|
|
488
|
+
if kind == "dict":
|
|
489
|
+
if typ["key"]["kind"] != "str":
|
|
490
|
+
self.fail(node, "PS2001", "JSON object keys are str")
|
|
491
|
+
if typ["value"]["kind"] == "bool":
|
|
492
|
+
self.fail(node, "PS2002", "dict values of bool are not lowered")
|
|
493
|
+
self._json_type(node, typ["value"], what)
|
|
494
|
+
return
|
|
495
|
+
self.fail(node, "PS2001", f"{what} does not lower {_show(typ)}")
|
|
496
|
+
|
|
497
|
+
def _struct_call(self, node: ast.Call, span: dict[str, Any], attr: str) -> dict[str, Any]:
|
|
498
|
+
if len(node.args) < 1:
|
|
499
|
+
self.fail(node, "PS2003", f"struct.{attr} expects a format")
|
|
500
|
+
fmt = _const_text(node.args[0])
|
|
501
|
+
if fmt is None:
|
|
502
|
+
self.fail(node, "PS2001", "struct format must be a string literal")
|
|
503
|
+
kinds = _struct_arg_kinds(fmt)
|
|
504
|
+
if kinds == "native":
|
|
505
|
+
self.fail(node, "PS2001", "struct native alignment is not lowered")
|
|
506
|
+
if not isinstance(kinds, list):
|
|
507
|
+
self.fail(node, "PS2001", "bad struct format")
|
|
508
|
+
format_arg = self._expr(node.args[0])
|
|
509
|
+
if attr == "pack":
|
|
510
|
+
if len(node.args) - 1 != len(kinds):
|
|
511
|
+
self.fail(node, "PS2003", f"struct.pack expects {len(kinds)} value(s) for this format")
|
|
512
|
+
args = [format_arg]
|
|
513
|
+
for kind, arg_node in zip(kinds, node.args[1:], strict=True):
|
|
514
|
+
arg = self._expr(arg_node)
|
|
515
|
+
got = arg["type"]["kind"]
|
|
516
|
+
if kind == "int" and got != "int":
|
|
517
|
+
self.fail(node, "PS1003", "struct.pack expected an int")
|
|
518
|
+
if kind == "float" and got == "float":
|
|
519
|
+
pass
|
|
520
|
+
elif kind == "float" and got == "int":
|
|
521
|
+
pass
|
|
522
|
+
elif kind == "float":
|
|
523
|
+
self.fail(node, "PS1003", "struct.pack expected a float")
|
|
524
|
+
if kind == "bytes" and got not in {"bytes", "str"}:
|
|
525
|
+
self.fail(node, "PS1003", "struct.pack expected bytes")
|
|
526
|
+
args.append(arg)
|
|
527
|
+
return _mod_call("struct", "pack", args, {"kind": "bytes"}, span)
|
|
528
|
+
if len(node.args) != 2:
|
|
529
|
+
self.fail(node, "PS2003", "struct.unpack expects 2 argument(s)")
|
|
530
|
+
distinct = set(kinds)
|
|
531
|
+
if len(distinct) > 1:
|
|
532
|
+
self.fail(node, "PS2001", "struct.unpack result types are mixed")
|
|
533
|
+
elem = "int" if not distinct else next(iter(distinct))
|
|
534
|
+
buffer = self._expr(node.args[1])
|
|
535
|
+
if buffer["type"]["kind"] not in {"bytes", "str"}:
|
|
536
|
+
self.fail(node, "PS1003", "struct.unpack buffer is bytes")
|
|
537
|
+
return _mod_call(
|
|
538
|
+
"struct",
|
|
539
|
+
"unpack",
|
|
540
|
+
[format_arg, buffer],
|
|
541
|
+
{"kind": "list", "elem": {"kind": elem}},
|
|
542
|
+
span,
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
def _hash_method(self, node: ast.Call, span: dict[str, Any], receiver: dict[str, Any]) -> dict[str, Any]:
|
|
546
|
+
assert isinstance(node.func, ast.Attribute)
|
|
547
|
+
self._reject_keywords(node)
|
|
548
|
+
name = node.func.attr
|
|
549
|
+
if node.args:
|
|
550
|
+
self.fail(node, "PS2003", f"Hash.{name} expects 0 argument(s)")
|
|
551
|
+
if name == "hexdigest":
|
|
552
|
+
return _mod_call("hashlib", "hexdigest", [receiver], {"kind": "str"}, span)
|
|
553
|
+
if name == "digest":
|
|
554
|
+
return _mod_call("hashlib", "digest", [receiver], {"kind": "bytes"}, span)
|
|
555
|
+
self.fail(node, "PS2001", f"Hash.{name} is not lowered")
|
|
556
|
+
raise AssertionError("unreachable")
|
|
557
|
+
|
|
558
|
+
def _pattern_method(self, node: ast.Call, span: dict[str, Any], receiver: dict[str, Any]) -> dict[str, Any]:
|
|
559
|
+
assert isinstance(node.func, ast.Attribute)
|
|
560
|
+
self._reject_keywords(node)
|
|
561
|
+
name = node.func.attr
|
|
562
|
+
pattern = {"kind": "attr", "value": receiver, "name": "pattern", "type": {"kind": "str"}, "span": span}
|
|
563
|
+
if name == "findall":
|
|
564
|
+
if len(node.args) != 1:
|
|
565
|
+
self.fail(node, "PS2003", "Pattern.findall expects 1 argument(s)")
|
|
566
|
+
ret, symbol = self._group_tuple(node, node.func.value)
|
|
567
|
+
text = self._expr(node.args[0])
|
|
568
|
+
if text["type"]["kind"] != "str":
|
|
569
|
+
self.fail(node, "PS1003", "Pattern.findall argument is " + _show(text["type"]) + ", expected str")
|
|
570
|
+
expr = _mod_call("re", "findall", [pattern, text], ret, span)
|
|
571
|
+
if symbol:
|
|
572
|
+
expr["symbol"] = symbol
|
|
573
|
+
return expr
|
|
574
|
+
if name == "sub":
|
|
575
|
+
if len(node.args) != 2:
|
|
576
|
+
self.fail(node, "PS2003", "Pattern.sub expects 2 argument(s)")
|
|
577
|
+
repl = self._expr(node.args[0])
|
|
578
|
+
text = self._expr(node.args[1])
|
|
579
|
+
if repl["type"]["kind"] != "str" or text["type"]["kind"] != "str":
|
|
580
|
+
self.fail(node, "PS1003", "Pattern.sub arguments are str")
|
|
581
|
+
return _mod_call("re", "sub", [pattern, repl, text], {"kind": "str"}, span)
|
|
582
|
+
if name == "split":
|
|
583
|
+
if len(node.args) != 1:
|
|
584
|
+
self.fail(node, "PS2003", "Pattern.split expects 1 argument(s)")
|
|
585
|
+
text = self._expr(node.args[0])
|
|
586
|
+
if text["type"]["kind"] != "str":
|
|
587
|
+
self.fail(node, "PS1003", "Pattern.split argument is " + _show(text["type"]) + ", expected str")
|
|
588
|
+
return _mod_call("re", "split", [pattern, text], {"kind": "list", "elem": {"kind": "str"}}, span)
|
|
589
|
+
self.fail(node, "PS2001", f"Pattern.{name} is not lowered")
|
|
590
|
+
raise AssertionError("unreachable")
|
|
591
|
+
|
|
592
|
+
def _group_tuple(self, node: ast.AST, pattern: ast.expr | None) -> tuple[dict[str, Any], str | None]:
|
|
593
|
+
text = self._pattern_literal(pattern) if pattern is not None else None
|
|
594
|
+
groups = _capturing_groups(text) if text is not None else 0
|
|
595
|
+
if groups > 4:
|
|
596
|
+
self.fail(node, "PS2002", "re.findall with more than four capturing groups is not lowered")
|
|
597
|
+
if groups >= 2:
|
|
598
|
+
elem = self._tuple_type([{"kind": "str"} for _ in range(groups)], node)
|
|
599
|
+
return {"kind": "list", "elem": elem}, "ps_re_findall_groups"
|
|
600
|
+
return {"kind": "list", "elem": {"kind": "str"}}, None
|
|
601
|
+
|
|
602
|
+
def _pattern_literal(self, node: ast.expr) -> str | None:
|
|
603
|
+
direct = _const_text(node)
|
|
604
|
+
if direct is not None:
|
|
605
|
+
return direct
|
|
606
|
+
if not isinstance(node, ast.Call) or not node.args:
|
|
607
|
+
return None
|
|
608
|
+
func = node.func
|
|
609
|
+
if isinstance(func, ast.Name):
|
|
610
|
+
binding = self.bindings.get(func.id)
|
|
611
|
+
if binding is not None and binding.kind == "func" and binding.module == "re" and binding.attr == "compile":
|
|
612
|
+
return _const_text(node.args[0])
|
|
613
|
+
return None
|
|
614
|
+
if isinstance(func, ast.Attribute):
|
|
615
|
+
resolved = self._resolve_module(func)
|
|
616
|
+
if resolved is not None and resolved[1] == "re" and resolved[2] == "compile":
|
|
617
|
+
return _const_text(node.args[0])
|
|
618
|
+
return None
|
|
619
|
+
|
|
620
|
+
def _method_call(self, node: ast.Call, span: dict[str, Any], receiver: dict[str, Any]) -> dict[str, Any]:
|
|
621
|
+
assert isinstance(node.func, ast.Attribute)
|
|
622
|
+
class_name = receiver["type"]["name"]
|
|
623
|
+
method = node.func.attr
|
|
624
|
+
if class_name not in self.classes:
|
|
625
|
+
self.fail(node, "PS2002", f"{class_name} did not lower")
|
|
626
|
+
info = self.classes[class_name]
|
|
627
|
+
sig = info["methods"].get(method)
|
|
628
|
+
kind = info.get("method_kind", {}).get(method, "method")
|
|
629
|
+
if sig is None or method == "__init__" or kind == "property":
|
|
630
|
+
if kind == "property":
|
|
631
|
+
self.fail(node, "PS2002", "a property is not called")
|
|
632
|
+
self.fail(node, "PS2001", f"{class_name}.{method} is not a method")
|
|
633
|
+
owner = info.get("method_owner", {}).get(method, class_name)
|
|
634
|
+
if kind in {"static", "class"}:
|
|
635
|
+
args = self._fill_args(node, sig[0], f"{class_name}.{method}")
|
|
636
|
+
else:
|
|
637
|
+
params = [param for param in sig[0] if param["name"] != "self"]
|
|
638
|
+
args = [receiver, *self._fill_args(node, params, f"{class_name}.{method}")]
|
|
639
|
+
return {
|
|
640
|
+
"kind": "call",
|
|
641
|
+
"callee": {"kind": "function", "name": f"cls.{owner}.{method}"},
|
|
642
|
+
"args": args,
|
|
643
|
+
"type": sig[1],
|
|
644
|
+
"span": span,
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
def _named_class_call(self, node: ast.Call, span: dict[str, Any], class_name: str) -> dict[str, Any]:
|
|
648
|
+
assert isinstance(node.func, ast.Attribute)
|
|
649
|
+
info = self.classes[class_name]
|
|
650
|
+
if info.get("enum"):
|
|
651
|
+
self.fail(node, "PS2002", f"{class_name} is not callable")
|
|
652
|
+
method = node.func.attr
|
|
653
|
+
kind = info.get("method_kind", {}).get(method)
|
|
654
|
+
sig = info["methods"].get(method)
|
|
655
|
+
if kind == "property":
|
|
656
|
+
self.fail(node, "PS2002", "a property is not called")
|
|
657
|
+
if kind not in {"static", "class"} or sig is None:
|
|
658
|
+
self.fail(node, "PS2002", f"{class_name}.{method} needs an instance")
|
|
659
|
+
owner = info.get("method_owner", {}).get(method, class_name)
|
|
660
|
+
args = self._fill_args(node, sig[0], f"{class_name}.{method}")
|
|
661
|
+
return {
|
|
662
|
+
"kind": "call",
|
|
663
|
+
"callee": {"kind": "function", "name": f"cls.{owner}.{method}"},
|
|
664
|
+
"args": args,
|
|
665
|
+
"type": sig[1],
|
|
666
|
+
"span": span,
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
def _abi_call(
|
|
670
|
+
self,
|
|
671
|
+
module: str,
|
|
672
|
+
name: str,
|
|
673
|
+
args: list[dict[str, Any]],
|
|
674
|
+
typ: dict[str, Any],
|
|
675
|
+
symbol: str,
|
|
676
|
+
span: dict[str, Any],
|
|
677
|
+
) -> dict[str, Any]:
|
|
678
|
+
expr = _mod_call(module, name, args, typ, span)
|
|
679
|
+
if symbol:
|
|
680
|
+
expr["symbol"] = symbol
|
|
681
|
+
return expr
|
|
682
|
+
|
|
683
|
+
def _synth_call(self, node: ast.Call, span: dict[str, Any], receiver: dict[str, Any]) -> dict[str, Any]:
|
|
684
|
+
assert isinstance(node.func, ast.Attribute)
|
|
685
|
+
class_name = receiver["type"]["name"]
|
|
686
|
+
if class_name == "Path" and node.func.attr == "read_text" and node.keywords:
|
|
687
|
+
return self._path_read_text(node, span, receiver)
|
|
688
|
+
self._reject_keywords(node)
|
|
689
|
+
spec = synth_method(class_name, node.func.attr)
|
|
690
|
+
if spec is None:
|
|
691
|
+
self.fail(node, "PS2001", f"{class_name}.{node.func.attr} is not lowered")
|
|
692
|
+
params = spec["params"]
|
|
693
|
+
if len(node.args) != len(params):
|
|
694
|
+
self.fail(node, "PS2003", f"{class_name}.{node.func.attr} expects {len(params)} argument(s)")
|
|
695
|
+
args = [receiver]
|
|
696
|
+
ret = spec["return"]
|
|
697
|
+
if ret == "elem":
|
|
698
|
+
ret = receiver["type"]["elem"]
|
|
699
|
+
for arg_node, want in zip(node.args, params, strict=True):
|
|
700
|
+
arg = self._expr(arg_node)
|
|
701
|
+
expected = receiver["type"]["elem"] if want == "elem" else want
|
|
702
|
+
if not types_equal(arg["type"], expected):
|
|
703
|
+
self.fail(node, "PS1003", f"{class_name}.{node.func.attr} argument has the wrong type")
|
|
704
|
+
args.append(arg)
|
|
705
|
+
module = _synth_module(class_name)
|
|
706
|
+
return self._abi_call(module, f"{class_name}.{node.func.attr}", args, ret, spec["symbol"], span)
|
|
707
|
+
|
|
708
|
+
def _reject_keywords(self, node: ast.Call) -> None:
|
|
709
|
+
if node.keywords:
|
|
710
|
+
self.fail(node, "PS2003", "keyword arguments are not lowered")
|
|
711
|
+
|
|
712
|
+
def _place_nodes(self, node: ast.Call, names: list[str]) -> list[ast.expr | None]:
|
|
713
|
+
if any(kw.arg is None for kw in node.keywords):
|
|
714
|
+
self.fail(node, "PS2002", "**kwargs are not lowered")
|
|
715
|
+
if len(node.args) > len(names):
|
|
716
|
+
self.fail(node, "PS2003", "too many positional arguments")
|
|
717
|
+
slots: list[ast.expr | None] = [None] * len(names)
|
|
718
|
+
for index, arg in enumerate(node.args):
|
|
719
|
+
slots[index] = arg
|
|
720
|
+
seen: set[str] = set()
|
|
721
|
+
for kw in node.keywords:
|
|
722
|
+
assert kw.arg is not None
|
|
723
|
+
if kw.arg not in names:
|
|
724
|
+
self.fail(kw, "PS2003", f"unexpected keyword argument {kw.arg}")
|
|
725
|
+
index = names.index(kw.arg)
|
|
726
|
+
if slots[index] is not None or kw.arg in seen:
|
|
727
|
+
self.fail(kw, "PS2003", f"{kw.arg} given more than once")
|
|
728
|
+
seen.add(kw.arg)
|
|
729
|
+
slots[index] = kw.value
|
|
730
|
+
return slots
|
|
731
|
+
|
|
732
|
+
def _fill_args(self, node: ast.Call, params: list[dict[str, Any]], what: str) -> list[dict[str, Any]]:
|
|
733
|
+
if any(kw.arg is None for kw in node.keywords):
|
|
734
|
+
self.fail(node, "PS2002", "**kwargs are not lowered")
|
|
735
|
+
if any(isinstance(arg, ast.Starred) for arg in node.args):
|
|
736
|
+
self.fail(node, "PS2002", "starred calls are not lowered")
|
|
737
|
+
var_index = next((index for index, param in enumerate(params) if param.get("vararg")), None)
|
|
738
|
+
if var_index is not None:
|
|
739
|
+
return self._fill_varargs(node, params, var_index, what)
|
|
740
|
+
if len(node.args) > len(params):
|
|
741
|
+
self.fail(node, "PS1003", f"{what} expects {len(params)} argument(s)")
|
|
742
|
+
slots: list[dict[str, Any] | None] = [None] * len(params)
|
|
743
|
+
names = [param["name"] for param in params]
|
|
744
|
+
for index, arg_node in enumerate(node.args):
|
|
745
|
+
slots[index] = self._expr(arg_node, params[index]["type"])
|
|
746
|
+
seen: set[str] = set()
|
|
747
|
+
for kw in node.keywords:
|
|
748
|
+
assert kw.arg is not None
|
|
749
|
+
if kw.arg not in names:
|
|
750
|
+
self.fail(kw, "PS2003", f"unexpected keyword argument {kw.arg}")
|
|
751
|
+
index = names.index(kw.arg)
|
|
752
|
+
if index < len(node.args) or kw.arg in seen:
|
|
753
|
+
self.fail(kw, "PS2003", f"{kw.arg} given more than once")
|
|
754
|
+
seen.add(kw.arg)
|
|
755
|
+
slots[index] = self._expr(kw.value, params[index]["type"])
|
|
756
|
+
args: list[dict[str, Any]] = []
|
|
757
|
+
for index, param in enumerate(params):
|
|
758
|
+
got = slots[index]
|
|
759
|
+
if got is None:
|
|
760
|
+
if "default" not in param:
|
|
761
|
+
self.fail(node, "PS1003", f"{what} expects {len(params)} argument(s)")
|
|
762
|
+
got = param["default"]
|
|
763
|
+
if not types_equal(got["type"], param["type"]):
|
|
764
|
+
self.fail(node, "PS1003", f"argument {param['name']} of {what} has the wrong type")
|
|
765
|
+
args.append(got)
|
|
766
|
+
return args
|
|
767
|
+
|
|
768
|
+
def _fill_varargs(
|
|
769
|
+
self, node: ast.Call, params: list[dict[str, Any]], var_index: int, what: str
|
|
770
|
+
) -> list[dict[str, Any]]:
|
|
771
|
+
fixed = params[:var_index]
|
|
772
|
+
varparam = params[var_index]
|
|
773
|
+
if any(kw.arg == varparam["name"] for kw in node.keywords):
|
|
774
|
+
self.fail(node, "PS2002", "a starred parameter is not passed by keyword")
|
|
775
|
+
positional = list(node.args[: len(fixed)])
|
|
776
|
+
extra = list(node.args[len(fixed) :])
|
|
777
|
+
slots: list[dict[str, Any] | None] = [None] * len(fixed)
|
|
778
|
+
names = [param["name"] for param in fixed]
|
|
779
|
+
for index, arg_node in enumerate(positional):
|
|
780
|
+
slots[index] = self._expr(arg_node, fixed[index]["type"])
|
|
781
|
+
seen: set[str] = set()
|
|
782
|
+
for kw in node.keywords:
|
|
783
|
+
assert kw.arg is not None
|
|
784
|
+
if kw.arg not in names:
|
|
785
|
+
self.fail(kw, "PS2003", f"unexpected keyword argument {kw.arg}")
|
|
786
|
+
index = names.index(kw.arg)
|
|
787
|
+
if index < len(positional) or kw.arg in seen:
|
|
788
|
+
self.fail(kw, "PS2003", f"{kw.arg} given more than once")
|
|
789
|
+
seen.add(kw.arg)
|
|
790
|
+
slots[index] = self._expr(kw.value, fixed[index]["type"])
|
|
791
|
+
args: list[dict[str, Any]] = []
|
|
792
|
+
for index, param in enumerate(fixed):
|
|
793
|
+
got = slots[index]
|
|
794
|
+
if got is None:
|
|
795
|
+
if "default" not in param:
|
|
796
|
+
self.fail(node, "PS1003", f"{what} expects {len(fixed)} argument(s)")
|
|
797
|
+
got = param["default"]
|
|
798
|
+
if not types_equal(got["type"], param["type"]):
|
|
799
|
+
self.fail(node, "PS1003", f"argument {param['name']} of {what} has the wrong type")
|
|
800
|
+
args.append(got)
|
|
801
|
+
elem = varparam["type"]["elem"]
|
|
802
|
+
elems = []
|
|
803
|
+
for arg_node in extra:
|
|
804
|
+
value = self._expr(arg_node, elem)
|
|
805
|
+
if not types_equal(value["type"], elem):
|
|
806
|
+
self.fail(node, "PS1003", f"argument {varparam['name']} of {what} has the wrong type")
|
|
807
|
+
elems.append(value)
|
|
808
|
+
span = span_of(node, self.filename)
|
|
809
|
+
args.append({"kind": "list", "elems": elems, "type": varparam["type"], "span": span})
|
|
810
|
+
return args
|
|
811
|
+
|
|
812
|
+
def _set_call(self, node: ast.Call, span: dict[str, Any], expect: dict[str, Any] | None) -> dict[str, Any]:
|
|
813
|
+
self._reject_keywords(node)
|
|
814
|
+
if len(node.args) > 1:
|
|
815
|
+
self.fail(node, "PS2003", "set expects 1 argument(s)")
|
|
816
|
+
if not node.args:
|
|
817
|
+
if expect is None or not expect.get("set"):
|
|
818
|
+
self.fail(node, "PS1001", "an empty set needs a set annotation")
|
|
819
|
+
return {"kind": "list", "elems": [], "type": expect, "span": span}
|
|
820
|
+
source = self._expr(node.args[0])
|
|
821
|
+
if source["type"]["kind"] != "list":
|
|
822
|
+
self.fail(node, "PS2002", "set() takes a list of int or str")
|
|
823
|
+
elem = source["type"]["elem"]
|
|
824
|
+
if elem["kind"] not in {"int", "str"} or source["type"].get("set"):
|
|
825
|
+
self.fail(node, "PS2002", "set elements are int or str")
|
|
826
|
+
typ = {"kind": "list", "elem": elem, "set": True}
|
|
827
|
+
if expect and expect.get("set"):
|
|
828
|
+
self._expect(node, typ, expect, "set")
|
|
829
|
+
folded = _const_set(source, typ, span)
|
|
830
|
+
if folded is not None:
|
|
831
|
+
return folded
|
|
832
|
+
dest = self._temp("set")
|
|
833
|
+
self.locals[dest] = typ
|
|
834
|
+
self.prelude.append(_assign(dest, {"kind": "list", "elems": [], "type": typ, "span": span}, span))
|
|
835
|
+
self.prelude.extend(self._set_absorb(dest, typ, source, span))
|
|
836
|
+
return _name(dest, typ, span)
|
|
837
|
+
|
|
838
|
+
def _frozenset_call(self, node: ast.Call, span: dict[str, Any], expect: dict[str, Any] | None) -> dict[str, Any]:
|
|
839
|
+
"""A list or set display of const str or const int, or one str constant, is the set list.
|
|
840
|
+
|
|
841
|
+
A str constant is the set of its characters, in order, first duplicate kept.
|
|
842
|
+
Bytes, a name, a comprehension, and anything else stay fenced. The value
|
|
843
|
+
is marked frozen so .add and .remove on it do not lower. There is no frozenset kind.
|
|
844
|
+
"""
|
|
845
|
+
if node.keywords or len(node.args) != 1:
|
|
846
|
+
self.fail(node, "PS2002", "call to frozenset is not lowered")
|
|
847
|
+
arg = node.args[0]
|
|
848
|
+
if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
|
|
849
|
+
return self._frozenset_str(node, arg, span, expect)
|
|
850
|
+
if not isinstance(arg, (ast.List, ast.Set)):
|
|
851
|
+
self.fail(node, "PS2002", "call to frozenset is not lowered")
|
|
852
|
+
folded = self._frozen_const_elems(arg)
|
|
853
|
+
if folded is None:
|
|
854
|
+
self.fail(node, "PS2002", "call to frozenset is not lowered")
|
|
855
|
+
kind, elems = folded
|
|
856
|
+
typ = {"kind": "list", "elem": {"kind": kind}, "set": True}
|
|
857
|
+
if expect is not None and expect.get("set"):
|
|
858
|
+
self._expect(node, typ, expect, "frozenset")
|
|
859
|
+
return {"kind": "list", "elems": elems, "type": typ, "span": span, "frozen": True}
|
|
860
|
+
|
|
861
|
+
def _frozen_const_elems(self, node: ast.List | ast.Set) -> tuple[str, list[dict[str, Any]]] | None:
|
|
862
|
+
if not node.elts or any(isinstance(elt, ast.Starred) for elt in node.elts):
|
|
863
|
+
return None
|
|
864
|
+
kinds: list[str] = []
|
|
865
|
+
for elt in node.elts:
|
|
866
|
+
if not isinstance(elt, ast.Constant) or isinstance(elt.value, bool):
|
|
867
|
+
return None
|
|
868
|
+
if isinstance(elt.value, str):
|
|
869
|
+
kinds.append("str")
|
|
870
|
+
elif isinstance(elt.value, int):
|
|
871
|
+
kinds.append("int")
|
|
872
|
+
else:
|
|
873
|
+
return None
|
|
874
|
+
if len(set(kinds)) != 1:
|
|
875
|
+
return None
|
|
876
|
+
elems: list[dict[str, Any]] = []
|
|
877
|
+
seen: list[Any] = []
|
|
878
|
+
for elt in node.elts:
|
|
879
|
+
value = self._expr(elt)
|
|
880
|
+
if value["value"] in seen:
|
|
881
|
+
continue
|
|
882
|
+
seen.append(value["value"])
|
|
883
|
+
elems.append(value)
|
|
884
|
+
return kinds[0], elems
|
|
885
|
+
|
|
886
|
+
def _frozenset_str(
|
|
887
|
+
self, node: ast.Call, arg: ast.Constant, span: dict[str, Any], expect: dict[str, Any] | None
|
|
888
|
+
) -> dict[str, Any]:
|
|
889
|
+
text = self._const(arg, span_of(arg, self.filename))["value"]
|
|
890
|
+
assert isinstance(text, str)
|
|
891
|
+
char_span = span_of(arg, self.filename)
|
|
892
|
+
elems: list[dict[str, Any]] = []
|
|
893
|
+
seen: list[str] = []
|
|
894
|
+
for ch in text:
|
|
895
|
+
if ch in seen:
|
|
896
|
+
continue
|
|
897
|
+
seen.append(ch)
|
|
898
|
+
elems.append({"kind": "const", "value": ch, "type": {"kind": "str"}, "span": char_span})
|
|
899
|
+
typ = {"kind": "list", "elem": {"kind": "str"}, "set": True}
|
|
900
|
+
if expect is not None and expect.get("set"):
|
|
901
|
+
self._expect(node, typ, expect, "frozenset")
|
|
902
|
+
return {"kind": "list", "elems": elems, "type": typ, "span": span, "frozen": True}
|
|
903
|
+
|
|
904
|
+
def _note_frozen(self, name: str, node: ast.expr) -> None:
|
|
905
|
+
if self._binding_is_frozen(node):
|
|
906
|
+
self.frozen_names.add(name)
|
|
907
|
+
else:
|
|
908
|
+
self.frozen_names.discard(name)
|
|
909
|
+
|
|
910
|
+
def _binding_is_frozen(self, node: ast.expr) -> bool:
|
|
911
|
+
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == "frozenset":
|
|
912
|
+
return True
|
|
913
|
+
if isinstance(node, ast.Name):
|
|
914
|
+
if node.id in self.frozen_names:
|
|
915
|
+
return True
|
|
916
|
+
found = self.constants.get(node.id)
|
|
917
|
+
return isinstance(found, dict) and bool(found.get("frozen"))
|
|
918
|
+
if isinstance(node, ast.IfExp):
|
|
919
|
+
return self._binding_is_frozen(node.body) or self._binding_is_frozen(node.orelse)
|
|
920
|
+
if isinstance(node, ast.BoolOp):
|
|
921
|
+
return any(self._binding_is_frozen(value) for value in node.values)
|
|
922
|
+
return False
|
|
923
|
+
|
|
924
|
+
def _bool_call(self, node: ast.Call, span: dict[str, Any]) -> dict[str, Any]:
|
|
925
|
+
self._reject_keywords(node)
|
|
926
|
+
if len(node.args) != 1:
|
|
927
|
+
self.fail(node, "PS2003", "bool expects 1 argument(s)")
|
|
928
|
+
arg = self._expr(node.args[0])
|
|
929
|
+
kind = arg["type"]["kind"]
|
|
930
|
+
if kind not in {"bool", "int", "float", "str", "bytes", "list", "dict"}:
|
|
931
|
+
self.fail(node, "PS2002", f"bool() of {kind} is not lowered")
|
|
932
|
+
return self._truthy(arg, span)
|
|
933
|
+
|
|
934
|
+
def _list_call(self, node: ast.Call, span: dict[str, Any], expect: dict[str, Any] | None) -> dict[str, Any]:
|
|
935
|
+
self._reject_keywords(node)
|
|
936
|
+
if len(node.args) != 1:
|
|
937
|
+
self.fail(node, "PS2003", "list expects 1 argument(s)")
|
|
938
|
+
source = self._expr(node.args[0])
|
|
939
|
+
if source["type"]["kind"] != "list":
|
|
940
|
+
self.fail(node, "PS2002", "list() takes a list, a set, or *args")
|
|
941
|
+
elem = source["type"]["elem"]
|
|
942
|
+
if elem["kind"] == "bool":
|
|
943
|
+
self.fail(node, "PS2002", "list[bool] is not lowered")
|
|
944
|
+
typ = {"kind": "list", "elem": elem}
|
|
945
|
+
if expect is not None and expect["kind"] == "list":
|
|
946
|
+
self._expect(node, typ, expect, "list")
|
|
947
|
+
if source["kind"] == "list" and not source["type"].get("set"):
|
|
948
|
+
return {"kind": "list", "elems": list(source["elems"]), "type": typ, "span": span}
|
|
949
|
+
return self._abi_call(
|
|
950
|
+
"builtins",
|
|
951
|
+
"list",
|
|
952
|
+
[
|
|
953
|
+
source,
|
|
954
|
+
_const_int(0, span),
|
|
955
|
+
_const_int(0, span),
|
|
956
|
+
_const_int(0, span),
|
|
957
|
+
_const_int(0, span),
|
|
958
|
+
_const_int(1, span),
|
|
959
|
+
],
|
|
960
|
+
typ,
|
|
961
|
+
"ps_seq_slice",
|
|
962
|
+
span,
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
def _dict_keys(self, node: ast.Call, span: dict[str, Any], receiver: dict[str, Any]) -> dict[str, Any]:
|
|
966
|
+
self._reject_keywords(node)
|
|
967
|
+
if node.args:
|
|
968
|
+
self.fail(node, "PS2003", "dict.keys expects 0 argument(s)")
|
|
969
|
+
key_t = receiver["type"]["key"]
|
|
970
|
+
if key_t["kind"] not in {"int", "str"}:
|
|
971
|
+
self.fail(node, "PS2002", "dict keys are int or str")
|
|
972
|
+
typ = {"kind": "list", "elem": key_t}
|
|
973
|
+
folded = _const_dict_keys(receiver, typ, span)
|
|
974
|
+
if folded is not None:
|
|
975
|
+
return folded
|
|
976
|
+
return self._abi_call("builtins", "dict.keys", [receiver], typ, "ps_dict_keys", span)
|
|
977
|
+
|
|
978
|
+
def _receiver_is_frozen(self, node: ast.Call, receiver: dict[str, Any]) -> bool:
|
|
979
|
+
if receiver.get("frozen"):
|
|
980
|
+
return True
|
|
981
|
+
assert isinstance(node.func, ast.Attribute)
|
|
982
|
+
return self._binding_is_frozen(node.func.value)
|
|
983
|
+
|
|
984
|
+
def _list_method(self, node: ast.Call, span: dict[str, Any], receiver: dict[str, Any]) -> dict[str, Any]:
|
|
985
|
+
assert isinstance(node.func, ast.Attribute)
|
|
986
|
+
if self._receiver_is_frozen(node, receiver):
|
|
987
|
+
self.fail(node, "PS2002", "frozenset has no mutating methods")
|
|
988
|
+
attr = node.func.attr
|
|
989
|
+
if attr == "append":
|
|
990
|
+
self._reject_keywords(node)
|
|
991
|
+
if len(node.args) != 1:
|
|
992
|
+
self.fail(node, "PS2003", "list.append expects 1 argument(s)")
|
|
993
|
+
if receiver["kind"] != "name":
|
|
994
|
+
self.fail(node, "PS2002", "list.append needs a name")
|
|
995
|
+
elem = receiver["type"]["elem"]
|
|
996
|
+
value = self._expr(node.args[0], elem)
|
|
997
|
+
if not types_equal(value["type"], elem):
|
|
998
|
+
self.fail(node, "PS1003", "list.append takes an element of that list")
|
|
999
|
+
self.prelude.extend(
|
|
1000
|
+
[
|
|
1001
|
+
{
|
|
1002
|
+
"kind": "expr",
|
|
1003
|
+
"expr": {
|
|
1004
|
+
"kind": "call",
|
|
1005
|
+
"callee": {"kind": "module", "module": "builtins", "name": "list.append"},
|
|
1006
|
+
"args": [receiver, value],
|
|
1007
|
+
"symbol": "ps_list_push",
|
|
1008
|
+
"type": {"kind": "none"},
|
|
1009
|
+
"span": span,
|
|
1010
|
+
},
|
|
1011
|
+
"span": span,
|
|
1012
|
+
}
|
|
1013
|
+
]
|
|
1014
|
+
)
|
|
1015
|
+
return _const_none(span)
|
|
1016
|
+
elem = receiver["type"]["elem"]
|
|
1017
|
+
# In-place mutations go through a prelude call; reads return a value.
|
|
1018
|
+
suffix = {"int": "i64", "float": "f64", "str": "buf", "bytes": "buf"}.get(elem["kind"])
|
|
1019
|
+
if attr == "sort":
|
|
1020
|
+
desc = _const_int(0, span)
|
|
1021
|
+
for kw in node.keywords:
|
|
1022
|
+
if kw.arg == "reverse":
|
|
1023
|
+
rv = self._expr(kw.value)
|
|
1024
|
+
if rv["kind"] != "const" or rv["type"]["kind"] != "bool":
|
|
1025
|
+
self.fail(node, "PS2003", "list.sort reverse is a constant bool")
|
|
1026
|
+
desc = _const_int(1 if rv["value"] else 0, span)
|
|
1027
|
+
else:
|
|
1028
|
+
self.fail(node, "PS2003", f"list.sort keyword {kw.arg} is not lowered")
|
|
1029
|
+
if node.args:
|
|
1030
|
+
self.fail(node, "PS2003", "list.sort expects 0 argument(s)")
|
|
1031
|
+
if suffix is None:
|
|
1032
|
+
self.fail(node, "PS2002", "list.sort is lowered for int, float, str, and bytes elements")
|
|
1033
|
+
call = self._abi_call(
|
|
1034
|
+
"builtins", "list.sort", [receiver, desc], {"kind": "none"}, f"ps_seq_sort_{suffix}", span
|
|
1035
|
+
)
|
|
1036
|
+
self.prelude.append({"kind": "expr", "expr": call, "span": span})
|
|
1037
|
+
return _const_none(span)
|
|
1038
|
+
self._reject_keywords(node)
|
|
1039
|
+
mutating = {"extend", "insert", "remove", "reverse"}
|
|
1040
|
+
if attr in mutating | {"index", "count", "pop"}:
|
|
1041
|
+
if suffix is None:
|
|
1042
|
+
self.fail(node, "PS2002", f"list.{attr} is lowered for int, float, str, and bytes elements")
|
|
1043
|
+
if attr == "extend":
|
|
1044
|
+
if len(node.args) != 1:
|
|
1045
|
+
self.fail(node, "PS2003", "list.extend expects 1 argument(s)")
|
|
1046
|
+
other = self._expr(node.args[0])
|
|
1047
|
+
if other["type"]["kind"] != "list" or other["type"].get("set"):
|
|
1048
|
+
self.fail(node, "PS1003", "list.extend takes a list")
|
|
1049
|
+
self._expect(node, other["type"]["elem"], elem, "list.extend element")
|
|
1050
|
+
call = self._abi_call(
|
|
1051
|
+
"builtins", "list.extend", [receiver, other], {"kind": "none"}, "ps_seq_extend", span
|
|
1052
|
+
)
|
|
1053
|
+
self.prelude.append({"kind": "expr", "expr": call, "span": span})
|
|
1054
|
+
return _const_none(span)
|
|
1055
|
+
if attr == "insert":
|
|
1056
|
+
if len(node.args) != 2:
|
|
1057
|
+
self.fail(node, "PS2003", "list.insert expects 2 argument(s)")
|
|
1058
|
+
index = self._expr(node.args[0])
|
|
1059
|
+
self._expect(node, index["type"], {"kind": "int"}, "list.insert index")
|
|
1060
|
+
value = self._expr(node.args[1], elem)
|
|
1061
|
+
self._expect(node, value["type"], elem, "list.insert element")
|
|
1062
|
+
call = self._abi_call(
|
|
1063
|
+
"builtins",
|
|
1064
|
+
"list.insert",
|
|
1065
|
+
[receiver, index, value],
|
|
1066
|
+
{"kind": "none"},
|
|
1067
|
+
f"ps_seq_insert_{suffix}",
|
|
1068
|
+
span,
|
|
1069
|
+
)
|
|
1070
|
+
self.prelude.append({"kind": "expr", "expr": call, "span": span})
|
|
1071
|
+
return _const_none(span)
|
|
1072
|
+
if attr == "remove":
|
|
1073
|
+
if len(node.args) != 1:
|
|
1074
|
+
self.fail(node, "PS2003", "list.remove expects 1 argument(s)")
|
|
1075
|
+
value = self._expr(node.args[0], elem)
|
|
1076
|
+
self._expect(node, value["type"], elem, "list.remove element")
|
|
1077
|
+
call = self._abi_call(
|
|
1078
|
+
"builtins", "list.remove", [receiver, value], {"kind": "none"}, f"ps_seq_remove_{suffix}", span
|
|
1079
|
+
)
|
|
1080
|
+
self.prelude.append({"kind": "expr", "expr": call, "span": span})
|
|
1081
|
+
return _const_none(span)
|
|
1082
|
+
if attr == "reverse":
|
|
1083
|
+
if node.args:
|
|
1084
|
+
self.fail(node, "PS2003", "list.reverse expects 0 argument(s)")
|
|
1085
|
+
call = self._abi_call("builtins", "list.reverse", [receiver], {"kind": "none"}, "ps_seq_reverse", span)
|
|
1086
|
+
self.prelude.append({"kind": "expr", "expr": call, "span": span})
|
|
1087
|
+
return _const_none(span)
|
|
1088
|
+
if attr in {"index", "count"}:
|
|
1089
|
+
if len(node.args) != 1:
|
|
1090
|
+
self.fail(node, "PS2003", f"list.{attr} expects 1 argument(s)")
|
|
1091
|
+
value = self._expr(node.args[0], elem)
|
|
1092
|
+
self._expect(node, value["type"], elem, f"list.{attr} element")
|
|
1093
|
+
return self._abi_call(
|
|
1094
|
+
"builtins", f"list.{attr}", [receiver, value], {"kind": "int"}, f"ps_seq_{attr}_{suffix}", span
|
|
1095
|
+
)
|
|
1096
|
+
if len(node.args) > 1:
|
|
1097
|
+
self.fail(node, "PS2003", "list.pop expects at most 1 argument(s)")
|
|
1098
|
+
index = self._expr(node.args[0]) if node.args else _const_int(-1, span)
|
|
1099
|
+
self._expect(node, index["type"], {"kind": "int"}, "list.pop index")
|
|
1100
|
+
return self._abi_call("builtins", "list.pop", [receiver, index], elem, f"ps_seq_pop_{suffix}", span)
|
|
1101
|
+
if attr == "clear":
|
|
1102
|
+
if node.args:
|
|
1103
|
+
self.fail(node, "PS2003", "list.clear expects 0 argument(s)")
|
|
1104
|
+
if receiver["kind"] != "name":
|
|
1105
|
+
self.fail(node, "PS2002", "list.clear needs a name")
|
|
1106
|
+
self.prelude.append(
|
|
1107
|
+
{
|
|
1108
|
+
"kind": "assign",
|
|
1109
|
+
"target": receiver["id"],
|
|
1110
|
+
"value": {"kind": "list", "elems": [], "type": receiver["type"], "span": span},
|
|
1111
|
+
"span": span,
|
|
1112
|
+
}
|
|
1113
|
+
)
|
|
1114
|
+
return _const_none(span)
|
|
1115
|
+
if attr == "copy":
|
|
1116
|
+
if node.args:
|
|
1117
|
+
self.fail(node, "PS2003", "list.copy expects 0 argument(s)")
|
|
1118
|
+
return self._abi_call(
|
|
1119
|
+
"builtins",
|
|
1120
|
+
"list.copy",
|
|
1121
|
+
[
|
|
1122
|
+
receiver,
|
|
1123
|
+
_const_int(0, span),
|
|
1124
|
+
_const_int(0, span),
|
|
1125
|
+
_const_int(0, span),
|
|
1126
|
+
_const_int(0, span),
|
|
1127
|
+
_const_int(1, span),
|
|
1128
|
+
],
|
|
1129
|
+
{"kind": "list", "elem": elem},
|
|
1130
|
+
"ps_seq_slice",
|
|
1131
|
+
span,
|
|
1132
|
+
)
|
|
1133
|
+
# The generic wording, byte-identical to the dispatch tail, keeps
|
|
1134
|
+
# the fenced list-method probes' IR unchanged.
|
|
1135
|
+
self.fail(node, "PS2001", f"call of attribute is not lowered ({_dotted(node.func)})")
|
|
1136
|
+
raise AssertionError("unreachable")
|
|
1137
|
+
|
|
1138
|
+
def _set_method(self, node: ast.Call, span: dict[str, Any], receiver: dict[str, Any]) -> dict[str, Any]:
|
|
1139
|
+
assert isinstance(node.func, ast.Attribute)
|
|
1140
|
+
if self._receiver_is_frozen(node, receiver):
|
|
1141
|
+
self.fail(node, "PS2002", "frozenset has no mutating methods")
|
|
1142
|
+
if node.func.attr != "add":
|
|
1143
|
+
self.fail(node, "PS2001", "set.add is the only lowered set method")
|
|
1144
|
+
self._reject_keywords(node)
|
|
1145
|
+
if len(node.args) != 1:
|
|
1146
|
+
self.fail(node, "PS2003", "set.add expects 1 argument(s)")
|
|
1147
|
+
if receiver["kind"] != "name":
|
|
1148
|
+
self.fail(node, "PS2002", "set.add needs a name")
|
|
1149
|
+
elem = receiver["type"]["elem"]
|
|
1150
|
+
value = self._expr(node.args[0], elem)
|
|
1151
|
+
if not types_equal(value["type"], elem):
|
|
1152
|
+
self.fail(node, "PS1003", "set.add takes an element of that set")
|
|
1153
|
+
self.prelude.extend(self._set_insert_stmts(receiver["id"], receiver["type"], value, span))
|
|
1154
|
+
return _const_none(span)
|
|
1155
|
+
|
|
1156
|
+
def _set_absorb(
|
|
1157
|
+
self, dest: str, typ: dict[str, Any], source: dict[str, Any], span: dict[str, Any]
|
|
1158
|
+
) -> list[dict[str, Any]]:
|
|
1159
|
+
elem = typ["elem"]
|
|
1160
|
+
int_t = {"kind": "int"}
|
|
1161
|
+
src = self._temp("src")
|
|
1162
|
+
idx = self._temp("idx")
|
|
1163
|
+
length = self._temp("len")
|
|
1164
|
+
item = self._temp("item")
|
|
1165
|
+
self.locals[src] = source["type"]
|
|
1166
|
+
self.locals[idx] = int_t
|
|
1167
|
+
self.locals[length] = int_t
|
|
1168
|
+
self.locals[item] = elem
|
|
1169
|
+
body = [
|
|
1170
|
+
_assign(
|
|
1171
|
+
item,
|
|
1172
|
+
{
|
|
1173
|
+
"kind": "subscript",
|
|
1174
|
+
"value": _name(src, source["type"], span),
|
|
1175
|
+
"index": _name(idx, int_t, span),
|
|
1176
|
+
"type": elem,
|
|
1177
|
+
"span": span,
|
|
1178
|
+
},
|
|
1179
|
+
span,
|
|
1180
|
+
),
|
|
1181
|
+
*self._set_insert_stmts(dest, typ, _name(item, elem, span), span),
|
|
1182
|
+
_assign(idx, _binary_int("+", _name(idx, int_t, span), _const_int(1, span), span), span),
|
|
1183
|
+
]
|
|
1184
|
+
return [
|
|
1185
|
+
_assign(src, source, span),
|
|
1186
|
+
_assign(
|
|
1187
|
+
length,
|
|
1188
|
+
{
|
|
1189
|
+
"kind": "call",
|
|
1190
|
+
"callee": {"kind": "builtin", "name": "len"},
|
|
1191
|
+
"args": [_name(src, source["type"], span)],
|
|
1192
|
+
"type": int_t,
|
|
1193
|
+
"span": span,
|
|
1194
|
+
},
|
|
1195
|
+
span,
|
|
1196
|
+
),
|
|
1197
|
+
_assign(idx, _const_int(0, span), span),
|
|
1198
|
+
{
|
|
1199
|
+
"kind": "while",
|
|
1200
|
+
"cond": _compare("<", _name(idx, int_t, span), _name(length, int_t, span), span),
|
|
1201
|
+
"body": body,
|
|
1202
|
+
"span": span,
|
|
1203
|
+
},
|
|
1204
|
+
]
|
|
1205
|
+
|
|
1206
|
+
def _set_insert_stmts(
|
|
1207
|
+
self, dest: str, typ: dict[str, Any], value: dict[str, Any], span: dict[str, Any]
|
|
1208
|
+
) -> list[dict[str, Any]]:
|
|
1209
|
+
elem = typ["elem"]
|
|
1210
|
+
slot = self._temp("elt")
|
|
1211
|
+
self.locals[slot] = elem
|
|
1212
|
+
needle = _name(slot, elem, span)
|
|
1213
|
+
hay = _name(dest, typ, span)
|
|
1214
|
+
missing = {
|
|
1215
|
+
"kind": "contains",
|
|
1216
|
+
"hay": hay,
|
|
1217
|
+
"needle": needle,
|
|
1218
|
+
"negated": True,
|
|
1219
|
+
"type": {"kind": "bool"},
|
|
1220
|
+
"span": span,
|
|
1221
|
+
}
|
|
1222
|
+
push = {
|
|
1223
|
+
"kind": "expr",
|
|
1224
|
+
"expr": {
|
|
1225
|
+
"kind": "call",
|
|
1226
|
+
"callee": {"kind": "module", "module": "builtins", "name": "list.append"},
|
|
1227
|
+
"args": [hay, needle],
|
|
1228
|
+
"symbol": "ps_list_push",
|
|
1229
|
+
"type": {"kind": "none"},
|
|
1230
|
+
"span": span,
|
|
1231
|
+
},
|
|
1232
|
+
"span": span,
|
|
1233
|
+
}
|
|
1234
|
+
return [
|
|
1235
|
+
_assign(slot, value, span),
|
|
1236
|
+
{"kind": "if", "cond": missing, "then": [push], "else": [], "span": span},
|
|
1237
|
+
]
|
|
1238
|
+
|
|
1239
|
+
def _path_read_text(self, node: ast.Call, span: dict[str, Any], receiver: dict[str, Any]) -> dict[str, Any]:
|
|
1240
|
+
if node.args:
|
|
1241
|
+
self.fail(node, "PS2003", "Path.read_text expects 0 argument(s)")
|
|
1242
|
+
if len(node.keywords) != 1 or node.keywords[0].arg != "encoding":
|
|
1243
|
+
self.fail(node, "PS2003", "Path.read_text accepts only encoding")
|
|
1244
|
+
encoding = node.keywords[0].value
|
|
1245
|
+
allowed = {"utf-8", "utf8", "ascii", "latin-1", "latin1"}
|
|
1246
|
+
if not isinstance(encoding, ast.Constant) or encoding.value not in allowed:
|
|
1247
|
+
self.fail(node, "PS2001", "Path.read_text encoding is utf-8, ascii, or latin-1")
|
|
1248
|
+
spec = synth_method("Path", "read_text")
|
|
1249
|
+
if spec is None:
|
|
1250
|
+
self.fail(node, "PS2001", "Path.read_text is not lowered")
|
|
1251
|
+
return self._abi_call("pathlib", "Path.read_text", [receiver], {"kind": "str"}, spec["symbol"], span)
|
|
1252
|
+
|
|
1253
|
+
def _one_str_pattern(self, node: ast.expr, value: dict[str, Any]) -> bool:
|
|
1254
|
+
"""True when this is re.compile of one str constant and no keywords.
|
|
1255
|
+
|
|
1256
|
+
That is the call a function already lowers to a Pattern. The stored
|
|
1257
|
+
value is that call, copied at each use. Anything else is not this form.
|
|
1258
|
+
"""
|
|
1259
|
+
if not isinstance(node, ast.Call) or node.keywords or len(node.args) != 1:
|
|
1260
|
+
return False
|
|
1261
|
+
arg = node.args[0]
|
|
1262
|
+
if not isinstance(arg, ast.Constant) or not isinstance(arg.value, str):
|
|
1263
|
+
return False
|
|
1264
|
+
if value.get("kind") != "call":
|
|
1265
|
+
return False
|
|
1266
|
+
callee = value.get("callee")
|
|
1267
|
+
if not isinstance(callee, dict):
|
|
1268
|
+
return False
|
|
1269
|
+
if callee.get("kind") != "module" or callee.get("module") != "re" or callee.get("name") != "compile":
|
|
1270
|
+
return False
|
|
1271
|
+
typ = value.get("type")
|
|
1272
|
+
return isinstance(typ, dict) and typ.get("kind") == "class" and typ.get("name") == "Pattern"
|
|
1273
|
+
|
|
1274
|
+
def _path_context(self, node: ast.Call, span: dict[str, Any], receiver: dict[str, Any]) -> dict[str, Any]:
|
|
1275
|
+
assert isinstance(node.func, ast.Attribute)
|
|
1276
|
+
if node.func.attr == "__enter__":
|
|
1277
|
+
if node.args or node.keywords:
|
|
1278
|
+
self.fail(node, "PS2003", "Path.__enter__ expects 0 argument(s)")
|
|
1279
|
+
return receiver
|
|
1280
|
+
if len(node.args) != 3 or node.keywords:
|
|
1281
|
+
self.fail(node, "PS2003", "Path.__exit__ expects 3 argument(s)")
|
|
1282
|
+
for arg in node.args:
|
|
1283
|
+
self._expr(arg)
|
|
1284
|
+
return _const_none(span)
|
|
1285
|
+
|
|
1286
|
+
def _all_any(self, node: ast.Call, span: dict[str, Any], want_all: bool) -> dict[str, Any]:
|
|
1287
|
+
label = "all" if want_all else "any"
|
|
1288
|
+
self._reject_keywords(node)
|
|
1289
|
+
if len(node.args) != 1:
|
|
1290
|
+
self.fail(node, "PS2003", f"{label} expects 1 argument(s)")
|
|
1291
|
+
source = self._expr(node.args[0])
|
|
1292
|
+
if source["type"]["kind"] != "list":
|
|
1293
|
+
self.fail(node, "PS2002", f"{label}() takes a list")
|
|
1294
|
+
elem = source["type"]["elem"]
|
|
1295
|
+
if elem["kind"] not in {"bool", "int", "float", "str", "bytes", "list", "dict"}:
|
|
1296
|
+
self.fail(node, "PS2002", f"{label}() of {_show(elem)} is not lowered")
|
|
1297
|
+
int_t = {"kind": "int"}
|
|
1298
|
+
bool_t = {"kind": "bool"}
|
|
1299
|
+
seq = self._temp("seq")
|
|
1300
|
+
idx = self._temp("idx")
|
|
1301
|
+
length = self._temp("len")
|
|
1302
|
+
result = self._temp("pred")
|
|
1303
|
+
self.locals[seq] = source["type"]
|
|
1304
|
+
self.locals[idx] = int_t
|
|
1305
|
+
self.locals[length] = int_t
|
|
1306
|
+
self.locals[result] = bool_t
|
|
1307
|
+
item = {
|
|
1308
|
+
"kind": "subscript",
|
|
1309
|
+
"value": _name(seq, source["type"], span),
|
|
1310
|
+
"index": _name(idx, int_t, span),
|
|
1311
|
+
"type": elem,
|
|
1312
|
+
"span": span,
|
|
1313
|
+
}
|
|
1314
|
+
truth = self._truthy(item, span)
|
|
1315
|
+
stop = _assign(idx, _name(length, int_t, span), span)
|
|
1316
|
+
step = _assign(idx, _binary_int("+", _name(idx, int_t, span), _const_int(1, span), span), span)
|
|
1317
|
+
if want_all:
|
|
1318
|
+
cond = {"kind": "unary", "op": "not", "operand": truth, "type": bool_t, "span": span}
|
|
1319
|
+
hit = _const_bool(False, span)
|
|
1320
|
+
else:
|
|
1321
|
+
cond = truth
|
|
1322
|
+
hit = _const_bool(True, span)
|
|
1323
|
+
self.prelude.extend(
|
|
1324
|
+
[
|
|
1325
|
+
_assign(seq, source, span),
|
|
1326
|
+
_assign(idx, _const_int(0, span), span),
|
|
1327
|
+
_assign(
|
|
1328
|
+
length,
|
|
1329
|
+
{
|
|
1330
|
+
"kind": "call",
|
|
1331
|
+
"callee": {"kind": "builtin", "name": "len"},
|
|
1332
|
+
"args": [_name(seq, source["type"], span)],
|
|
1333
|
+
"type": int_t,
|
|
1334
|
+
"span": span,
|
|
1335
|
+
},
|
|
1336
|
+
span,
|
|
1337
|
+
),
|
|
1338
|
+
_assign(result, _const_bool(want_all, span), span),
|
|
1339
|
+
{
|
|
1340
|
+
"kind": "while",
|
|
1341
|
+
"cond": _compare("<", _name(idx, int_t, span), _name(length, int_t, span), span),
|
|
1342
|
+
"body": [
|
|
1343
|
+
{
|
|
1344
|
+
"kind": "if",
|
|
1345
|
+
"cond": cond,
|
|
1346
|
+
"then": [_assign(result, hit, span), stop],
|
|
1347
|
+
"else": [step],
|
|
1348
|
+
"span": span,
|
|
1349
|
+
}
|
|
1350
|
+
],
|
|
1351
|
+
"span": span,
|
|
1352
|
+
},
|
|
1353
|
+
]
|
|
1354
|
+
)
|
|
1355
|
+
return _name(result, bool_t, span)
|
|
1356
|
+
|
|
1357
|
+
|
|
1358
|
+
def _const_set(source: dict[str, Any], typ: dict[str, Any], span: dict[str, Any]) -> dict[str, Any] | None:
|
|
1359
|
+
"""A constant list becomes the set list the IR already stores, first occurrence kept."""
|
|
1360
|
+
|
|
1361
|
+
if source.get("kind") != "list":
|
|
1362
|
+
return None
|
|
1363
|
+
elems: list[dict[str, Any]] = []
|
|
1364
|
+
seen: list[Any] = []
|
|
1365
|
+
for item in source["elems"]:
|
|
1366
|
+
if item.get("kind") != "const":
|
|
1367
|
+
return None
|
|
1368
|
+
if item["value"] in seen:
|
|
1369
|
+
continue
|
|
1370
|
+
seen.append(item["value"])
|
|
1371
|
+
elems.append(item)
|
|
1372
|
+
return {"kind": "list", "elems": elems, "type": typ, "span": span}
|
|
1373
|
+
|
|
1374
|
+
|
|
1375
|
+
def _const_dict_keys(receiver: dict[str, Any], typ: dict[str, Any], span: dict[str, Any]) -> dict[str, Any] | None:
|
|
1376
|
+
"""A dict literal's keys are already the list the IR builds, in insertion order."""
|
|
1377
|
+
|
|
1378
|
+
if receiver.get("kind") != "dict":
|
|
1379
|
+
return None
|
|
1380
|
+
elems: list[dict[str, Any]] = []
|
|
1381
|
+
seen: list[Any] = []
|
|
1382
|
+
for pair in receiver["pairs"]:
|
|
1383
|
+
key = pair["key"]
|
|
1384
|
+
if key.get("kind") != "const":
|
|
1385
|
+
return None
|
|
1386
|
+
if key["value"] in seen:
|
|
1387
|
+
continue
|
|
1388
|
+
seen.append(key["value"])
|
|
1389
|
+
elems.append(key)
|
|
1390
|
+
return {"kind": "list", "elems": elems, "type": typ, "span": span}
|
|
1391
|
+
|
|
1392
|
+
|
|
1393
|
+
_ASCII_SPACE = " \t\n\r\v\f"
|
|
1394
|
+
_FLOAT_WORDS = {"inf": "inf", "infinity": "inf", "nan": "nan"}
|
|
1395
|
+
|
|
1396
|
+
|
|
1397
|
+
def _float_special_arg(node: ast.Call) -> str | None:
|
|
1398
|
+
"""float() of an inf or nan spelling is the float const math.inf / math.nan already use.
|
|
1399
|
+
|
|
1400
|
+
Optional leading sign and surrounding ASCII whitespace. A name, a number
|
|
1401
|
+
string, bytes, and any other argument stay on the ordinary path.
|
|
1402
|
+
"""
|
|
1403
|
+
if node.keywords or len(node.args) != 1:
|
|
1404
|
+
return None
|
|
1405
|
+
arg = node.args[0]
|
|
1406
|
+
if not isinstance(arg, ast.Constant) or not isinstance(arg.value, str):
|
|
1407
|
+
return None
|
|
1408
|
+
body = arg.value.strip(_ASCII_SPACE)
|
|
1409
|
+
sign = ""
|
|
1410
|
+
if body[:1] in "+-":
|
|
1411
|
+
sign = body[0]
|
|
1412
|
+
body = body[1:]
|
|
1413
|
+
kind = _FLOAT_WORDS.get(body.lower())
|
|
1414
|
+
if kind is None:
|
|
1415
|
+
return None
|
|
1416
|
+
if kind == "nan":
|
|
1417
|
+
return "nan"
|
|
1418
|
+
if sign == "-":
|
|
1419
|
+
return "-inf"
|
|
1420
|
+
return "inf"
|
|
1421
|
+
|
|
1422
|
+
|
|
1423
|
+
def _const_text(node: ast.expr) -> str | None:
|
|
1424
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
1425
|
+
return node.value
|
|
1426
|
+
return None
|
|
1427
|
+
|
|
1428
|
+
|
|
1429
|
+
def _capturing_groups(pattern: str) -> int:
|
|
1430
|
+
index = 0
|
|
1431
|
+
groups = 0
|
|
1432
|
+
size = len(pattern)
|
|
1433
|
+
while index < size:
|
|
1434
|
+
char = pattern[index]
|
|
1435
|
+
if char == "\\":
|
|
1436
|
+
index += 2
|
|
1437
|
+
continue
|
|
1438
|
+
if char == "[":
|
|
1439
|
+
index += 1
|
|
1440
|
+
if index < size and pattern[index] == "^":
|
|
1441
|
+
index += 1
|
|
1442
|
+
if index < size and pattern[index] == "]":
|
|
1443
|
+
index += 1
|
|
1444
|
+
while index < size and pattern[index] != "]":
|
|
1445
|
+
if pattern[index] == "\\" and index + 1 < size:
|
|
1446
|
+
index += 2
|
|
1447
|
+
continue
|
|
1448
|
+
index += 1
|
|
1449
|
+
if index < size:
|
|
1450
|
+
index += 1
|
|
1451
|
+
continue
|
|
1452
|
+
if char == "(":
|
|
1453
|
+
if index + 2 < size and pattern[index + 1] == "?" and pattern[index + 2] == ":":
|
|
1454
|
+
index += 3
|
|
1455
|
+
continue
|
|
1456
|
+
if index + 1 < size and pattern[index + 1] == "?":
|
|
1457
|
+
index += 1
|
|
1458
|
+
continue
|
|
1459
|
+
groups += 1
|
|
1460
|
+
index += 1
|
|
1461
|
+
return groups
|
|
1462
|
+
|
|
1463
|
+
|
|
1464
|
+
def _struct_arg_kinds(fmt: str) -> list[str] | str:
|
|
1465
|
+
"""Argument kinds for a struct format, or `native` / `bad`."""
|
|
1466
|
+
|
|
1467
|
+
index = 0
|
|
1468
|
+
kinds: list[str] = []
|
|
1469
|
+
size = len(fmt)
|
|
1470
|
+
while index < size:
|
|
1471
|
+
char = fmt[index]
|
|
1472
|
+
if char in " \t\n\r<>!=":
|
|
1473
|
+
index += 1
|
|
1474
|
+
continue
|
|
1475
|
+
if char == "@":
|
|
1476
|
+
return "native"
|
|
1477
|
+
count = 1
|
|
1478
|
+
if char.isdigit():
|
|
1479
|
+
count = 0
|
|
1480
|
+
while index < size and fmt[index].isdigit():
|
|
1481
|
+
count = count * 10 + (ord(fmt[index]) - 48)
|
|
1482
|
+
index += 1
|
|
1483
|
+
if index >= size:
|
|
1484
|
+
return "bad"
|
|
1485
|
+
char = fmt[index]
|
|
1486
|
+
index += 1
|
|
1487
|
+
if char == "s":
|
|
1488
|
+
kinds.append("bytes")
|
|
1489
|
+
continue
|
|
1490
|
+
if char == "x":
|
|
1491
|
+
continue
|
|
1492
|
+
if char in "bBhHiIlLqQ":
|
|
1493
|
+
kinds.extend(["int"] * count)
|
|
1494
|
+
continue
|
|
1495
|
+
if char in "fd":
|
|
1496
|
+
kinds.extend(["float"] * count)
|
|
1497
|
+
continue
|
|
1498
|
+
return "bad"
|
|
1499
|
+
return kinds
|