jaclang 0.8.6__py3-none-any.whl → 0.8.7__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.
Potentially problematic release.
This version of jaclang might be problematic. Click here for more details.
- jaclang/cli/cli.md +3 -3
- jaclang/cli/cli.py +25 -11
- jaclang/cli/cmdreg.py +1 -140
- jaclang/compiler/passes/main/pyast_gen_pass.py +13 -3
- jaclang/compiler/passes/main/pyast_load_pass.py +14 -20
- jaclang/compiler/passes/main/tests/fixtures/checker_binary_op.jac +21 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_call_expr_class.jac +12 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_cyclic_symbol.jac +4 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_expr_call.jac +9 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_import_missing_module.jac +13 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_magic_call.jac +17 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_mod_path.jac +8 -0
- jaclang/compiler/passes/main/tests/test_checker_pass.py +74 -0
- jaclang/compiler/passes/main/type_checker_pass.py +24 -5
- jaclang/compiler/type_system/operations.py +104 -0
- jaclang/compiler/type_system/type_evaluator.py +141 -2
- jaclang/langserve/engine.jac +135 -91
- jaclang/langserve/server.jac +21 -14
- jaclang/langserve/tests/server_test/test_lang_serve.py +2 -5
- jaclang/langserve/tests/test_dev_server.py +1 -1
- jaclang/langserve/tests/test_server.py +9 -2
- jaclang/langserve/utils.jac +44 -48
- jaclang/tests/fixtures/jac_run_py_bugs.py +18 -0
- jaclang/tests/fixtures/jac_run_py_import.py +13 -0
- jaclang/tests/fixtures/lambda_arg_annotation.jac +15 -0
- jaclang/tests/fixtures/lambda_self.jac +18 -0
- jaclang/tests/test_cli.py +66 -17
- jaclang/utils/module_resolver.py +1 -1
- {jaclang-0.8.6.dist-info → jaclang-0.8.7.dist-info}/METADATA +3 -2
- {jaclang-0.8.6.dist-info → jaclang-0.8.7.dist-info}/RECORD +32 -20
- {jaclang-0.8.6.dist-info → jaclang-0.8.7.dist-info}/WHEEL +1 -1
- {jaclang-0.8.6.dist-info → jaclang-0.8.7.dist-info}/entry_points.txt +0 -0
jaclang/cli/cli.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# **Jac Language Command Line Interface (CLI)**
|
|
2
2
|
|
|
3
|
-
Jac Language CLI
|
|
3
|
+
git Jac Language CLI provides a variety of commands to facilitate users. Additionally, Jac language offers users the ability to define custom CLI commands through plugins. This document aims to provide an overview of each command along with clear usage instructions.
|
|
4
4
|
|
|
5
5
|
> [!TIP]
|
|
6
|
-
>
|
|
6
|
+
> Use `jac --help` to see available commands and usage.
|
|
7
7
|
|
|
8
8
|
### Click one of the default commands below and see the usage.
|
|
9
|
-
- [tool](#tool) , [run](#run) , [clean](#clean) , [format](#format) , [check](#check) , [build](#build)
|
|
9
|
+
- [tool](#tool) , [run](#run) , [clean](#clean) , [format](#format) , [check](#check) , [build](#build) , [enter](#enter) , [test](#test)
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
jaclang/cli/cli.py
CHANGED
|
@@ -11,7 +11,7 @@ from pathlib import Path
|
|
|
11
11
|
from typing import Optional
|
|
12
12
|
|
|
13
13
|
import jaclang.compiler.unitree as uni
|
|
14
|
-
from jaclang.cli.cmdreg import
|
|
14
|
+
from jaclang.cli.cmdreg import cmd_registry
|
|
15
15
|
from jaclang.compiler.passes.main import PyastBuildPass
|
|
16
16
|
from jaclang.compiler.program import JacProgram
|
|
17
17
|
from jaclang.runtimelib.builtin import printgraph
|
|
@@ -132,7 +132,7 @@ def run(
|
|
|
132
132
|
jac run myprogram.jac --session mysession
|
|
133
133
|
jac run myprogram.jac --no-main
|
|
134
134
|
"""
|
|
135
|
-
# if no session specified, check if it was defined
|
|
135
|
+
# if no session specified, check if it was defined via global CLI args
|
|
136
136
|
# otherwise default to jaclang.session
|
|
137
137
|
base, mod, mach = proc_file_sess(filename, session)
|
|
138
138
|
lng = filename.split(".")[-1]
|
|
@@ -662,6 +662,14 @@ def start_cli() -> None:
|
|
|
662
662
|
- None
|
|
663
663
|
"""
|
|
664
664
|
parser = cmd_registry.parser
|
|
665
|
+
# Default to `run` when a file is provided without a subcommand
|
|
666
|
+
raw_argv = sys.argv[1:]
|
|
667
|
+
if (
|
|
668
|
+
raw_argv
|
|
669
|
+
and not raw_argv[0].startswith("-")
|
|
670
|
+
and raw_argv[0].lower().endswith((".jac", ".jir", ".py"))
|
|
671
|
+
):
|
|
672
|
+
sys.argv = [sys.argv[0], "run"] + raw_argv
|
|
665
673
|
args = parser.parse_args()
|
|
666
674
|
cmd_registry.args = args
|
|
667
675
|
|
|
@@ -671,16 +679,22 @@ def start_cli() -> None:
|
|
|
671
679
|
print("Jac path:", __file__)
|
|
672
680
|
return
|
|
673
681
|
|
|
682
|
+
if args.command is None:
|
|
683
|
+
parser.print_help()
|
|
684
|
+
return
|
|
685
|
+
|
|
674
686
|
command = cmd_registry.get(args.command)
|
|
675
|
-
if command:
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
687
|
+
if not command:
|
|
688
|
+
print(f"Unknown command: {args.command}", file=sys.stderr)
|
|
689
|
+
parser.print_help()
|
|
690
|
+
return
|
|
691
|
+
|
|
692
|
+
args_dict = vars(args)
|
|
693
|
+
args_dict.pop("command")
|
|
694
|
+
args_dict.pop("version", None)
|
|
695
|
+
ret = command.call(**args_dict)
|
|
696
|
+
if ret:
|
|
697
|
+
print(ret)
|
|
684
698
|
|
|
685
699
|
|
|
686
700
|
if __name__ == "__main__":
|
jaclang/cli/cmdreg.py
CHANGED
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
-
import cmd
|
|
7
6
|
import inspect
|
|
8
|
-
import pprint
|
|
9
7
|
import re
|
|
10
8
|
from typing import Callable, Dict, Optional
|
|
11
9
|
|
|
@@ -241,141 +239,4 @@ class CommandRegistry:
|
|
|
241
239
|
cmd_registry = CommandRegistry()
|
|
242
240
|
|
|
243
241
|
|
|
244
|
-
|
|
245
|
-
"""Command shell for the command line interface."""
|
|
246
|
-
|
|
247
|
-
intro = "Welcome to the Jac CLI!"
|
|
248
|
-
prompt = "jac> "
|
|
249
|
-
cmd_reg: CommandRegistry
|
|
250
|
-
|
|
251
|
-
def __init__(self, cmd_reg: CommandRegistry) -> None:
|
|
252
|
-
"""Initialize a CommandShell instance."""
|
|
253
|
-
self.cmd_reg = cmd_reg
|
|
254
|
-
super().__init__()
|
|
255
|
-
|
|
256
|
-
def do_exit(self, arg: list) -> bool:
|
|
257
|
-
"""Exit the command shell."""
|
|
258
|
-
return True
|
|
259
|
-
|
|
260
|
-
def default(self, line: str) -> None:
|
|
261
|
-
"""Process the command line input."""
|
|
262
|
-
args = vars(self.cmd_reg.parser.parse_args(line.split()))
|
|
263
|
-
command = self.cmd_reg.get(args["command"])
|
|
264
|
-
if command:
|
|
265
|
-
args.pop("command")
|
|
266
|
-
ret = command.call(**args)
|
|
267
|
-
if ret:
|
|
268
|
-
ret_str = pprint.pformat(ret, indent=2)
|
|
269
|
-
self.stdout.write(f"{ret_str}\n")
|
|
270
|
-
|
|
271
|
-
def do_help(self, arg: str) -> None:
|
|
272
|
-
"""Jac CLI help implementation."""
|
|
273
|
-
|
|
274
|
-
def get_info(name: str, doc: str, args: dict[str, inspect.Parameter]) -> None:
|
|
275
|
-
"""Format and display detailed command information."""
|
|
276
|
-
# Format the command header
|
|
277
|
-
self.stdout.write(f"\n{'=' * 80}\n")
|
|
278
|
-
self.stdout.write(f"COMMAND: {name}\n")
|
|
279
|
-
self.stdout.write(f"{'=' * 80}\n\n")
|
|
280
|
-
|
|
281
|
-
# Format the command description
|
|
282
|
-
doc_lines = doc.strip().split("\n")
|
|
283
|
-
for line in doc_lines:
|
|
284
|
-
self.stdout.write(f"{line}\n")
|
|
285
|
-
|
|
286
|
-
# Format the command arguments
|
|
287
|
-
if args:
|
|
288
|
-
self.stdout.write("\nARGUMENTS:\n")
|
|
289
|
-
for param_name, param in args.items():
|
|
290
|
-
# Get parameter type
|
|
291
|
-
type_name = (
|
|
292
|
-
param.annotation.__name__
|
|
293
|
-
if hasattr(param.annotation, "__name__")
|
|
294
|
-
else str(param.annotation)
|
|
295
|
-
)
|
|
296
|
-
|
|
297
|
-
# Format default value if present
|
|
298
|
-
default_str = ""
|
|
299
|
-
if param.default is not param.empty:
|
|
300
|
-
default_str = f" (default: {param.default})"
|
|
301
|
-
|
|
302
|
-
# Format required/optional status
|
|
303
|
-
req_str = (
|
|
304
|
-
" [required]"
|
|
305
|
-
if param.default is param.empty and param_name != "args"
|
|
306
|
-
else " [optional]"
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
self.stdout.write(
|
|
310
|
-
f" {param_name}: {type_name}{default_str}{req_str}\n"
|
|
311
|
-
)
|
|
312
|
-
else:
|
|
313
|
-
self.stdout.write("\nNo arguments\n")
|
|
314
|
-
|
|
315
|
-
# Format usage examples
|
|
316
|
-
command_parser = self.cmd_reg.sub_parsers.choices[name]
|
|
317
|
-
self.stdout.write(f"\nUSAGE:\n {command_parser.format_usage()[7:]}\n")
|
|
318
|
-
|
|
319
|
-
# Extract and format examples from docstring if present
|
|
320
|
-
if "Examples:" in doc:
|
|
321
|
-
examples_section = doc.split("Examples:")[1].strip()
|
|
322
|
-
example_lines = examples_section.split("\n")
|
|
323
|
-
self.stdout.write("\nEXAMPLES:\n")
|
|
324
|
-
for example in example_lines:
|
|
325
|
-
if example.strip():
|
|
326
|
-
self.stdout.write(f" {example.strip()}\n")
|
|
327
|
-
|
|
328
|
-
self.stdout.write("\n")
|
|
329
|
-
|
|
330
|
-
if arg == "all":
|
|
331
|
-
# Display all commands with their details
|
|
332
|
-
command_details = self.cmd_reg.get_all_commands()
|
|
333
|
-
for name, (doc, args) in sorted(command_details.items()):
|
|
334
|
-
get_info(name, doc, args)
|
|
335
|
-
|
|
336
|
-
elif arg:
|
|
337
|
-
# Display help for a specific command
|
|
338
|
-
command = self.cmd_reg.get(arg)
|
|
339
|
-
if command:
|
|
340
|
-
doc = command.func.__doc__ or "No help available."
|
|
341
|
-
args = command.sig.parameters
|
|
342
|
-
get_info(arg, doc, args)
|
|
343
|
-
else:
|
|
344
|
-
self.stdout.write(f"\nUnknown command: '{arg}'\n")
|
|
345
|
-
self.stdout.write("Type 'help' to see available commands.\n")
|
|
346
|
-
else:
|
|
347
|
-
# Display general help information
|
|
348
|
-
self.stdout.write("\n")
|
|
349
|
-
self.stdout.write("JAC PROGRAMMING LANGUAGE COMMAND LINE INTERFACE\n")
|
|
350
|
-
self.stdout.write("=" * 50 + "\n\n")
|
|
351
|
-
|
|
352
|
-
self.stdout.write("AVAILABLE COMMANDS:\n")
|
|
353
|
-
|
|
354
|
-
# Get all command names and sort them alphabetically
|
|
355
|
-
command_names = sorted(self.cmd_reg.registry.keys())
|
|
356
|
-
|
|
357
|
-
# Get brief descriptions for each command
|
|
358
|
-
command_descriptions: dict[str, str] = {}
|
|
359
|
-
for name in command_names:
|
|
360
|
-
command = self.cmd_reg.get(name)
|
|
361
|
-
if command and command.func.__doc__:
|
|
362
|
-
# Extract the first line of the docstring as a brief description
|
|
363
|
-
brief = command.func.__doc__.split("\n")[0].strip()
|
|
364
|
-
command_descriptions[name] = brief
|
|
365
|
-
else:
|
|
366
|
-
command_descriptions[name] = "No description available"
|
|
367
|
-
|
|
368
|
-
# Display commands with their brief descriptions
|
|
369
|
-
for name in command_names:
|
|
370
|
-
self.stdout.write(
|
|
371
|
-
f" {name.ljust(15)} - {command_descriptions[name]}\n"
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
self.stdout.write("\nFor detailed information on a specific command:\n")
|
|
375
|
-
self.stdout.write(" help <command>\n")
|
|
376
|
-
|
|
377
|
-
self.stdout.write("\nTo see detailed information for all commands:\n")
|
|
378
|
-
self.stdout.write(" help all\n")
|
|
379
|
-
|
|
380
|
-
self.stdout.write("\nTo exit the Jac CLI:\n")
|
|
381
|
-
self.stdout.write(" exit\n\n")
|
|
242
|
+
# Shell mode removed; interactive cmd-based shell is no longer supported.
|
|
@@ -1002,7 +1002,8 @@ class PyastGenPass(UniPass):
|
|
|
1002
1002
|
def exit_func_signature(self, node: uni.FuncSignature) -> None:
|
|
1003
1003
|
params = (
|
|
1004
1004
|
[self.sync(ast3.arg(arg="self", annotation=None))]
|
|
1005
|
-
if (abl := node.
|
|
1005
|
+
if (abl := node.parent)
|
|
1006
|
+
and isinstance(abl, uni.Ability)
|
|
1006
1007
|
and abl.is_method
|
|
1007
1008
|
and not node.is_static
|
|
1008
1009
|
and not node.is_in_py_class
|
|
@@ -2123,6 +2124,10 @@ class PyastGenPass(UniPass):
|
|
|
2123
2124
|
]
|
|
2124
2125
|
|
|
2125
2126
|
def exit_lambda_expr(self, node: uni.LambdaExpr) -> None:
|
|
2127
|
+
# Python lambda expressions don't support type annotations
|
|
2128
|
+
if node.signature:
|
|
2129
|
+
self._remove_lambda_param_annotations(node.signature)
|
|
2130
|
+
|
|
2126
2131
|
node.gen.py_ast = [
|
|
2127
2132
|
self.sync(
|
|
2128
2133
|
ast3.Lambda(
|
|
@@ -2144,6 +2149,11 @@ class PyastGenPass(UniPass):
|
|
|
2144
2149
|
)
|
|
2145
2150
|
]
|
|
2146
2151
|
|
|
2152
|
+
def _remove_lambda_param_annotations(self, signature: uni.FuncSignature) -> None:
|
|
2153
|
+
for param in signature.params:
|
|
2154
|
+
if param.gen.py_ast and isinstance(param.gen.py_ast[0], ast3.arg):
|
|
2155
|
+
param.gen.py_ast[0].annotation = None
|
|
2156
|
+
|
|
2147
2157
|
def exit_unary_expr(self, node: uni.UnaryExpr) -> None:
|
|
2148
2158
|
op_cls = UNARY_OP_MAP.get(node.op.name)
|
|
2149
2159
|
if op_cls:
|
|
@@ -2400,7 +2410,7 @@ class PyastGenPass(UniPass):
|
|
|
2400
2410
|
self.sync(
|
|
2401
2411
|
ast3.Attribute(
|
|
2402
2412
|
value=cast(ast3.expr, node.target.gen.py_ast[0]),
|
|
2403
|
-
attr=
|
|
2413
|
+
attr=node.right.sym_name,
|
|
2404
2414
|
ctx=cast(ast3.expr_context, node.right.py_ctx_func()),
|
|
2405
2415
|
)
|
|
2406
2416
|
)
|
|
@@ -2997,7 +3007,7 @@ class PyastGenPass(UniPass):
|
|
|
2997
3007
|
node.gen.py_ast = [self.sync(op_cls())]
|
|
2998
3008
|
|
|
2999
3009
|
def exit_name(self, node: uni.Name) -> None:
|
|
3000
|
-
name = node.sym_name
|
|
3010
|
+
name = node.sym_name
|
|
3001
3011
|
node.gen.py_ast = [self.sync(ast3.Name(id=name, ctx=node.py_ctx_func()))]
|
|
3002
3012
|
|
|
3003
3013
|
def exit_float(self, node: uni.Float) -> None:
|
|
@@ -141,17 +141,17 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
|
|
|
141
141
|
|
|
142
142
|
reserved_keywords = [v for _, v in TOKEN_MAP.items()]
|
|
143
143
|
|
|
144
|
-
value = node.name if node.name not in reserved_keywords else f"<>{node.name}"
|
|
145
144
|
name = uni.Name(
|
|
146
145
|
orig_src=self.orig_src,
|
|
147
146
|
name=Tok.NAME,
|
|
148
|
-
value=
|
|
147
|
+
value=node.name,
|
|
149
148
|
line=node.lineno,
|
|
150
149
|
end_line=node.end_lineno if node.end_lineno else node.lineno,
|
|
151
150
|
col_start=node.col_offset,
|
|
152
151
|
col_end=node.col_offset + len(node.name),
|
|
153
152
|
pos_start=0,
|
|
154
153
|
pos_end=0,
|
|
154
|
+
is_kwesc=(node.name in reserved_keywords),
|
|
155
155
|
)
|
|
156
156
|
body = [self.convert(i) for i in node.body]
|
|
157
157
|
valid = [i for i in body if isinstance(i, (uni.CodeBlockStmt))]
|
|
@@ -285,8 +285,8 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
|
|
|
285
285
|
and body_stmt.signature.params
|
|
286
286
|
):
|
|
287
287
|
for param in body_stmt.signature.params:
|
|
288
|
-
if param.name.value == "self":
|
|
289
|
-
param.type_tag
|
|
288
|
+
if param.name.value == "self" and param.type_tag:
|
|
289
|
+
param.type_tag.tag.value = name.value
|
|
290
290
|
doc = (
|
|
291
291
|
body[0].expr
|
|
292
292
|
if isinstance(body[0], uni.ExprStmt)
|
|
@@ -748,17 +748,14 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
|
|
|
748
748
|
attribute = uni.Name(
|
|
749
749
|
orig_src=self.orig_src,
|
|
750
750
|
name=Tok.NAME,
|
|
751
|
-
value=
|
|
752
|
-
("<>" + node.attr)
|
|
753
|
-
if node.attr == "init"
|
|
754
|
-
else "init" if node.attr == "__init__" else node.attr
|
|
755
|
-
),
|
|
751
|
+
value="init" if node.attr == "__init__" else node.attr,
|
|
756
752
|
line=node.lineno,
|
|
757
753
|
end_line=node.end_lineno if node.end_lineno else node.lineno,
|
|
758
754
|
col_start=node.col_offset,
|
|
759
755
|
col_end=node.col_offset + len(node.attr),
|
|
760
756
|
pos_start=0,
|
|
761
757
|
pos_end=0,
|
|
758
|
+
is_kwesc=node.attr == "init",
|
|
762
759
|
)
|
|
763
760
|
if isinstance(value, uni.Expr):
|
|
764
761
|
return uni.AtomTrailer(
|
|
@@ -1680,18 +1677,17 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
|
|
|
1680
1677
|
for _, v in TOKEN_MAP.items()
|
|
1681
1678
|
if v not in ["float", "int", "str", "bool", "self"]
|
|
1682
1679
|
]
|
|
1683
|
-
|
|
1684
|
-
value = node.id if node.id not in reserved_keywords else f"<>{node.id}"
|
|
1685
1680
|
ret = uni.Name(
|
|
1686
1681
|
orig_src=self.orig_src,
|
|
1687
1682
|
name=Tok.NAME,
|
|
1688
|
-
value=
|
|
1683
|
+
value=node.id,
|
|
1689
1684
|
line=node.lineno,
|
|
1690
1685
|
end_line=node.end_lineno if node.end_lineno else node.lineno,
|
|
1691
1686
|
col_start=node.col_offset,
|
|
1692
1687
|
col_end=node.col_offset + len(node.id),
|
|
1693
1688
|
pos_start=0,
|
|
1694
1689
|
pos_end=0,
|
|
1690
|
+
is_kwesc=(node.id in reserved_keywords),
|
|
1695
1691
|
)
|
|
1696
1692
|
return ret
|
|
1697
1693
|
|
|
@@ -1735,18 +1731,18 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
|
|
|
1735
1731
|
|
|
1736
1732
|
names: list[uni.NameAtom] = []
|
|
1737
1733
|
for name in node.names:
|
|
1738
|
-
value = name if name not in reserved_keywords else f"<>{name}"
|
|
1739
1734
|
names.append(
|
|
1740
1735
|
uni.Name(
|
|
1741
1736
|
orig_src=self.orig_src,
|
|
1742
1737
|
name=Tok.NAME,
|
|
1743
|
-
value=
|
|
1738
|
+
value=name,
|
|
1744
1739
|
line=node.lineno,
|
|
1745
1740
|
end_line=node.end_lineno if node.end_lineno else node.lineno,
|
|
1746
1741
|
col_start=node.col_offset,
|
|
1747
1742
|
col_end=node.col_offset + len(name),
|
|
1748
1743
|
pos_start=0,
|
|
1749
1744
|
pos_end=0,
|
|
1745
|
+
is_kwesc=(name in reserved_keywords),
|
|
1750
1746
|
)
|
|
1751
1747
|
)
|
|
1752
1748
|
return uni.NonLocalStmt(target=names, kid=names)
|
|
@@ -2056,17 +2052,17 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
|
|
|
2056
2052
|
if v not in ["float", "int", "str", "bool", "self"]
|
|
2057
2053
|
]
|
|
2058
2054
|
|
|
2059
|
-
value = node.arg if node.arg not in reserved_keywords else f"<>{node.arg}"
|
|
2060
2055
|
name = uni.Name(
|
|
2061
2056
|
orig_src=self.orig_src,
|
|
2062
2057
|
name=Tok.NAME,
|
|
2063
|
-
value=
|
|
2058
|
+
value=node.arg,
|
|
2064
2059
|
line=node.lineno,
|
|
2065
2060
|
end_line=node.end_lineno if node.end_lineno else node.lineno,
|
|
2066
2061
|
col_start=node.col_offset,
|
|
2067
2062
|
col_end=node.col_offset + len(node.arg),
|
|
2068
2063
|
pos_start=0,
|
|
2069
2064
|
pos_end=0,
|
|
2065
|
+
is_kwesc=(node.arg in reserved_keywords),
|
|
2070
2066
|
)
|
|
2071
2067
|
ann_expr = (
|
|
2072
2068
|
self.convert(node.annotation)
|
|
@@ -2343,19 +2339,17 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
|
|
|
2343
2339
|
from jaclang.compiler import TOKEN_MAP
|
|
2344
2340
|
|
|
2345
2341
|
reserved_keywords = [v for _, v in TOKEN_MAP.items()]
|
|
2346
|
-
arg_value = (
|
|
2347
|
-
node.arg if node.arg not in reserved_keywords else f"<>{node.arg}"
|
|
2348
|
-
)
|
|
2349
2342
|
arg = uni.Name(
|
|
2350
2343
|
orig_src=self.orig_src,
|
|
2351
2344
|
name=Tok.NAME,
|
|
2352
|
-
value=
|
|
2345
|
+
value=node.arg,
|
|
2353
2346
|
line=node.lineno,
|
|
2354
2347
|
end_line=node.end_lineno if node.end_lineno else node.lineno,
|
|
2355
2348
|
col_start=node.col_offset,
|
|
2356
2349
|
col_end=node.col_offset + len(node.arg if node.arg else "_"),
|
|
2357
2350
|
pos_start=0,
|
|
2358
2351
|
pos_end=0,
|
|
2352
|
+
is_kwesc=(node.arg in reserved_keywords),
|
|
2359
2353
|
)
|
|
2360
2354
|
value = self.convert(node.value)
|
|
2361
2355
|
if isinstance(value, uni.Expr):
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
node B {
|
|
2
|
+
def __mul__(other: B) -> int {
|
|
3
|
+
return 0;
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
node A {
|
|
8
|
+
def __add__(other: A) -> B {
|
|
9
|
+
return B();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
with entry {
|
|
14
|
+
a: A = A();
|
|
15
|
+
|
|
16
|
+
r1: B = a + a; # <-- Ok
|
|
17
|
+
r2: A = a + a; # <-- Error
|
|
18
|
+
|
|
19
|
+
r3: int = (a+a) * B(); # <-- Ok
|
|
20
|
+
r4: str = (a+a) * B(); # <-- Error
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import from scipy { stats, optimize }
|
|
2
|
+
import from utils.fake_helpers { helper_func }
|
|
3
|
+
import from non_existent_module { foo }
|
|
4
|
+
import nonexistent_module as nm;
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
with entry {
|
|
8
|
+
a: int = stats.norm.cdf(0);
|
|
9
|
+
d: float = optimize.minimize_scalar(lambda x: int: x ** 2).fun;
|
|
10
|
+
result = helper_func();
|
|
11
|
+
b: int = foo();
|
|
12
|
+
c = nm.some_func();
|
|
13
|
+
}
|
|
@@ -81,6 +81,80 @@ class TypeCheckerPassTests(TestCase):
|
|
|
81
81
|
^^^^^^^^^^^^^^
|
|
82
82
|
""", program.errors_had[0].pretty_print())
|
|
83
83
|
|
|
84
|
+
def test_call_expr(self) -> None:
|
|
85
|
+
path = self.fixture_abs_path("checker_expr_call.jac")
|
|
86
|
+
program = JacProgram()
|
|
87
|
+
mod = program.compile(path)
|
|
88
|
+
TypeCheckPass(ir_in=mod, prog=program)
|
|
89
|
+
self.assertEqual(len(program.errors_had), 1)
|
|
90
|
+
self._assert_error_pretty_found("""
|
|
91
|
+
s: str = foo();
|
|
92
|
+
^^^^^^^^^^^^^^
|
|
93
|
+
""", program.errors_had[0].pretty_print())
|
|
94
|
+
|
|
95
|
+
def test_call_expr_magic(self) -> None:
|
|
96
|
+
path = self.fixture_abs_path("checker_magic_call.jac")
|
|
97
|
+
program = JacProgram()
|
|
98
|
+
mod = program.compile(path)
|
|
99
|
+
TypeCheckPass(ir_in=mod, prog=program)
|
|
100
|
+
self.assertEqual(len(program.errors_had), 1)
|
|
101
|
+
self._assert_error_pretty_found("""
|
|
102
|
+
b: Bar = fn()(); # <-- Ok
|
|
103
|
+
f: Foo = fn()(); # <-- Error
|
|
104
|
+
^^^^^^^^^^^^^^^^
|
|
105
|
+
""", program.errors_had[0].pretty_print())
|
|
106
|
+
|
|
107
|
+
def test_binary_op(self) -> None:
|
|
108
|
+
program = JacProgram()
|
|
109
|
+
mod = program.compile(self.fixture_abs_path("checker_binary_op.jac"))
|
|
110
|
+
TypeCheckPass(ir_in=mod, prog=program)
|
|
111
|
+
self.assertEqual(len(program.errors_had), 2)
|
|
112
|
+
self._assert_error_pretty_found("""
|
|
113
|
+
r2: A = a + a; # <-- Error
|
|
114
|
+
^^^^^^^^^^^^^
|
|
115
|
+
""", program.errors_had[0].pretty_print())
|
|
116
|
+
self._assert_error_pretty_found("""
|
|
117
|
+
r4: str = (a+a) * B(); # <-- Error
|
|
118
|
+
^^^^^^^^^^^^^^^^^^^^^
|
|
119
|
+
""", program.errors_had[1].pretty_print())
|
|
120
|
+
|
|
121
|
+
def test_checker_call_expr_class(self) -> None:
|
|
122
|
+
path = self.fixture_abs_path("checker_call_expr_class.jac")
|
|
123
|
+
program = JacProgram()
|
|
124
|
+
mod = program.compile(path)
|
|
125
|
+
TypeCheckPass(ir_in=mod, prog=program)
|
|
126
|
+
self.assertEqual(len(program.errors_had), 1)
|
|
127
|
+
self._assert_error_pretty_found("""
|
|
128
|
+
inst.i = 'str'; # <-- Error
|
|
129
|
+
^^^^^^^^^^^^^^
|
|
130
|
+
""", program.errors_had[0].pretty_print())
|
|
131
|
+
|
|
132
|
+
def test_checker_mod_path(self) -> None:
|
|
133
|
+
path = self.fixture_abs_path("checker_mod_path.jac")
|
|
134
|
+
program = JacProgram()
|
|
135
|
+
mod = program.compile(path)
|
|
136
|
+
TypeCheckPass(ir_in=mod, prog=program)
|
|
137
|
+
self.assertEqual(len(program.errors_had), 1)
|
|
138
|
+
self._assert_error_pretty_found("""
|
|
139
|
+
a:int = uni.Module; # <-- Error
|
|
140
|
+
^^^^^^^^^^^^^^
|
|
141
|
+
""", program.errors_had[0].pretty_print())
|
|
142
|
+
|
|
143
|
+
def test_checker_import_missing_module(self) -> None:
|
|
144
|
+
path = self.fixture_abs_path("checker_import_missing_module.jac")
|
|
145
|
+
program = JacProgram()
|
|
146
|
+
mod = program.compile(path)
|
|
147
|
+
TypeCheckPass(ir_in=mod, prog=program)
|
|
148
|
+
self.assertEqual(len(program.errors_had), 0)
|
|
149
|
+
|
|
150
|
+
def test_cyclic_symbol(self) -> None:
|
|
151
|
+
path = self.fixture_abs_path("checker_cyclic_symbol.jac")
|
|
152
|
+
program = JacProgram()
|
|
153
|
+
mod = program.compile(path)
|
|
154
|
+
# This will result in a stack overflow if not handled properly.
|
|
155
|
+
# So the fact that it has 0 errors means it passed.
|
|
156
|
+
TypeCheckPass(ir_in=mod, prog=program)
|
|
157
|
+
self.assertEqual(len(program.errors_had), 0)
|
|
84
158
|
|
|
85
159
|
def _assert_error_pretty_found(self, needle: str, haystack: str) -> None:
|
|
86
160
|
for line in [line.strip() for line in needle.splitlines() if line.strip()]:
|
|
@@ -36,16 +36,28 @@ class TypeCheckPass(UniPass):
|
|
|
36
36
|
# Cache the builtins module once it parsed.
|
|
37
37
|
_BUILTINS_MODULE: uni.Module | None = None
|
|
38
38
|
|
|
39
|
+
# REVIEW: Making the evaluator a static (singleton) variable to make sure only one
|
|
40
|
+
# instance is used across mulitple compilation units. This can also be attached to an
|
|
41
|
+
# attribute of JacProgram, however the evaluator is a temproary object that we dont
|
|
42
|
+
# want bound to the program for long term, Also the program is the one that will be
|
|
43
|
+
# dumped in the compiled bundle.
|
|
44
|
+
_EVALUATOR: TypeEvaluator | None = None
|
|
45
|
+
|
|
39
46
|
def before_pass(self) -> None:
|
|
40
47
|
"""Initialize the checker pass."""
|
|
41
48
|
self._load_builtins_stub_module()
|
|
42
49
|
self._insert_builtin_symbols()
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
@property
|
|
52
|
+
def evaluator(self) -> TypeEvaluator:
|
|
53
|
+
"""Return the type evaluator."""
|
|
54
|
+
if TypeCheckPass._EVALUATOR is None:
|
|
55
|
+
assert TypeCheckPass._BUILTINS_MODULE is not None
|
|
56
|
+
TypeCheckPass._EVALUATOR = TypeEvaluator(
|
|
57
|
+
builtins_module=TypeCheckPass._BUILTINS_MODULE,
|
|
58
|
+
program=self.prog,
|
|
59
|
+
)
|
|
60
|
+
return TypeCheckPass._EVALUATOR
|
|
49
61
|
|
|
50
62
|
# --------------------------------------------------------------------------
|
|
51
63
|
# Internal helper functions
|
|
@@ -126,3 +138,10 @@ class TypeCheckPass(UniPass):
|
|
|
126
138
|
def exit_atom_trailer(self, node: uni.AtomTrailer) -> None:
|
|
127
139
|
"""Handle the atom trailer node."""
|
|
128
140
|
self.evaluator.get_type_of_expression(node)
|
|
141
|
+
|
|
142
|
+
def exit_func_call(self, node: uni.FuncCall) -> None:
|
|
143
|
+
"""Handle the function call node."""
|
|
144
|
+
# TODO:
|
|
145
|
+
# 1. Function Existence & Callable Validation
|
|
146
|
+
# 2. Argument Matching(count, types, names)
|
|
147
|
+
self.evaluator.get_type_of_expression(node)
|