jaclang 0.8.6__py3-none-any.whl → 0.8.8__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.

Files changed (103) hide show
  1. jaclang/cli/cli.md +3 -3
  2. jaclang/cli/cli.py +37 -37
  3. jaclang/cli/cmdreg.py +45 -140
  4. jaclang/compiler/constant.py +0 -1
  5. jaclang/compiler/jac.lark +3 -6
  6. jaclang/compiler/larkparse/jac_parser.py +2 -2
  7. jaclang/compiler/parser.py +213 -34
  8. jaclang/compiler/passes/main/__init__.py +2 -4
  9. jaclang/compiler/passes/main/def_use_pass.py +0 -4
  10. jaclang/compiler/passes/main/predynamo_pass.py +221 -0
  11. jaclang/compiler/passes/main/pyast_gen_pass.py +83 -55
  12. jaclang/compiler/passes/main/pyast_load_pass.py +66 -40
  13. jaclang/compiler/passes/main/sym_tab_build_pass.py +1 -1
  14. jaclang/compiler/passes/main/tests/fixtures/checker/import_sym.jac +2 -0
  15. jaclang/compiler/passes/main/tests/fixtures/checker/import_sym_test.jac +6 -0
  16. jaclang/compiler/passes/main/tests/fixtures/checker/imported_sym.jac +5 -0
  17. jaclang/compiler/passes/main/tests/fixtures/checker_arg_param_match.jac +37 -0
  18. jaclang/compiler/passes/main/tests/fixtures/checker_arity.jac +18 -0
  19. jaclang/compiler/passes/main/tests/fixtures/checker_binary_op.jac +21 -0
  20. jaclang/compiler/passes/main/tests/fixtures/checker_call_expr_class.jac +12 -0
  21. jaclang/compiler/passes/main/tests/fixtures/checker_cat_is_animal.jac +18 -0
  22. jaclang/compiler/passes/main/tests/fixtures/checker_cyclic_symbol.jac +4 -0
  23. jaclang/compiler/passes/main/tests/fixtures/checker_expr_call.jac +9 -0
  24. jaclang/compiler/passes/main/tests/fixtures/checker_float.jac +7 -0
  25. jaclang/compiler/passes/main/tests/fixtures/checker_import_missing_module.jac +13 -0
  26. jaclang/compiler/passes/main/tests/fixtures/checker_magic_call.jac +17 -0
  27. jaclang/compiler/passes/main/tests/fixtures/checker_mod_path.jac +8 -0
  28. jaclang/compiler/passes/main/tests/fixtures/checker_param_types.jac +11 -0
  29. jaclang/compiler/passes/main/tests/fixtures/checker_self_type.jac +9 -0
  30. jaclang/compiler/passes/main/tests/fixtures/checker_sym_inherit.jac +42 -0
  31. jaclang/compiler/passes/main/tests/fixtures/predynamo_fix3.jac +43 -0
  32. jaclang/compiler/passes/main/tests/fixtures/predynamo_where_assign.jac +13 -0
  33. jaclang/compiler/passes/main/tests/fixtures/predynamo_where_return.jac +11 -0
  34. jaclang/compiler/passes/main/tests/test_checker_pass.py +265 -0
  35. jaclang/compiler/passes/main/tests/test_predynamo_pass.py +57 -0
  36. jaclang/compiler/passes/main/type_checker_pass.py +36 -61
  37. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +204 -44
  38. jaclang/compiler/passes/tool/jac_formatter_pass.py +119 -69
  39. jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +3 -3
  40. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +4 -5
  41. jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +171 -11
  42. jaclang/compiler/passes/transform.py +12 -8
  43. jaclang/compiler/program.py +14 -6
  44. jaclang/compiler/tests/fixtures/jac_import_py_files.py +4 -0
  45. jaclang/compiler/tests/fixtures/jac_module.jac +3 -0
  46. jaclang/compiler/tests/fixtures/multiple_syntax_errors.jac +10 -0
  47. jaclang/compiler/tests/fixtures/python_module.py +1 -0
  48. jaclang/compiler/tests/test_importer.py +39 -0
  49. jaclang/compiler/tests/test_parser.py +49 -0
  50. jaclang/compiler/type_system/operations.py +104 -0
  51. jaclang/compiler/type_system/type_evaluator.py +470 -47
  52. jaclang/compiler/type_system/type_utils.py +246 -0
  53. jaclang/compiler/type_system/types.py +58 -2
  54. jaclang/compiler/unitree.py +79 -94
  55. jaclang/langserve/engine.jac +253 -230
  56. jaclang/langserve/server.jac +46 -15
  57. jaclang/langserve/tests/fixtures/circle.jac +3 -3
  58. jaclang/langserve/tests/fixtures/circle_err.jac +3 -3
  59. jaclang/langserve/tests/fixtures/circle_pure.test.jac +3 -3
  60. jaclang/langserve/tests/fixtures/completion_test_err.jac +10 -0
  61. jaclang/langserve/tests/server_test/circle_template.jac +80 -0
  62. jaclang/langserve/tests/server_test/glob_template.jac +4 -0
  63. jaclang/langserve/tests/server_test/test_lang_serve.py +154 -312
  64. jaclang/langserve/tests/server_test/utils.py +153 -116
  65. jaclang/langserve/tests/test_dev_server.py +1 -1
  66. jaclang/langserve/tests/test_server.py +30 -86
  67. jaclang/langserve/utils.jac +56 -63
  68. jaclang/runtimelib/machine.py +7 -0
  69. jaclang/runtimelib/meta_importer.py +27 -1
  70. jaclang/runtimelib/tests/fixtures/custom_access_validation.jac +1 -1
  71. jaclang/runtimelib/tests/fixtures/savable_object.jac +2 -2
  72. jaclang/settings.py +18 -14
  73. jaclang/tests/fixtures/abc_check.jac +3 -3
  74. jaclang/tests/fixtures/arch_rel_import_creation.jac +12 -12
  75. jaclang/tests/fixtures/chandra_bugs2.jac +3 -3
  76. jaclang/tests/fixtures/create_dynamic_archetype.jac +13 -13
  77. jaclang/tests/fixtures/jac_run_py_bugs.py +18 -0
  78. jaclang/tests/fixtures/jac_run_py_import.py +13 -0
  79. jaclang/tests/fixtures/lambda_arg_annotation.jac +15 -0
  80. jaclang/tests/fixtures/lambda_self.jac +18 -0
  81. jaclang/tests/fixtures/maxfail_run_test.jac +4 -4
  82. jaclang/tests/fixtures/params/param_syntax_err.jac +9 -0
  83. jaclang/tests/fixtures/params/test_complex_params.jac +42 -0
  84. jaclang/tests/fixtures/params/test_failing_kwonly.jac +207 -0
  85. jaclang/tests/fixtures/params/test_failing_posonly.jac +116 -0
  86. jaclang/tests/fixtures/params/test_failing_varargs.jac +300 -0
  87. jaclang/tests/fixtures/params/test_kwonly_params.jac +29 -0
  88. jaclang/tests/fixtures/py2jac_params.py +8 -0
  89. jaclang/tests/fixtures/run_test.jac +4 -4
  90. jaclang/tests/test_cli.py +103 -18
  91. jaclang/tests/test_language.py +74 -16
  92. jaclang/utils/helpers.py +47 -2
  93. jaclang/utils/module_resolver.py +11 -1
  94. jaclang/utils/test.py +8 -0
  95. jaclang/utils/treeprinter.py +0 -18
  96. {jaclang-0.8.6.dist-info → jaclang-0.8.8.dist-info}/METADATA +3 -3
  97. {jaclang-0.8.6.dist-info → jaclang-0.8.8.dist-info}/RECORD +99 -62
  98. {jaclang-0.8.6.dist-info → jaclang-0.8.8.dist-info}/WHEEL +1 -1
  99. jaclang/compiler/passes/main/inheritance_pass.py +0 -131
  100. jaclang/langserve/dev_engine.jac +0 -645
  101. jaclang/langserve/dev_server.jac +0 -201
  102. jaclang/langserve/tests/server_test/code_test.py +0 -0
  103. {jaclang-0.8.6.dist-info → jaclang-0.8.8.dist-info}/entry_points.txt +0 -0
@@ -1132,6 +1132,13 @@ class JacBasics:
1132
1132
  return JacMachineInterface.get_edges(origin, path.destinations[-1])
1133
1133
  return origin
1134
1134
 
1135
+ @staticmethod
1136
+ async def arefs(
1137
+ path: DataSpatialPath | NodeArchetype | list[NodeArchetype],
1138
+ ) -> None:
1139
+ """Jac's apply_dir stmt feature."""
1140
+ pass
1141
+
1135
1142
  @staticmethod
1136
1143
  def filter(
1137
1144
  items: list[Archetype],
@@ -9,7 +9,8 @@ from typing import Optional, Sequence
9
9
 
10
10
  from jaclang.runtimelib.machine import JacMachine as Jac
11
11
  from jaclang.runtimelib.machine import JacMachineInterface
12
- from jaclang.utils.module_resolver import get_jac_search_paths
12
+ from jaclang.settings import settings
13
+ from jaclang.utils.module_resolver import get_jac_search_paths, get_py_search_paths
13
14
 
14
15
 
15
16
  class JacMetaImporter(importlib.abc.MetaPathFinder, importlib.abc.Loader):
@@ -48,6 +49,30 @@ class JacMetaImporter(importlib.abc.MetaPathFinder, importlib.abc.Loader):
48
49
  return importlib.util.spec_from_file_location(
49
50
  fullname, candidate_path + ".jac", loader=self
50
51
  )
52
+
53
+ # TODO: We can remove it once python modules are fully supported in jac
54
+ if path is None and settings.pyfile_raise:
55
+ if settings.pyfile_raise_full:
56
+ paths_to_search = get_jac_search_paths()
57
+ else:
58
+ paths_to_search = get_py_search_paths()
59
+ for search_path in paths_to_search:
60
+ candidate_path = os.path.join(search_path, *module_path_parts)
61
+ # Check for directory package
62
+ if os.path.isdir(candidate_path):
63
+ init_file = os.path.join(candidate_path, "__init__.py")
64
+ if os.path.isfile(init_file):
65
+ return importlib.util.spec_from_file_location(
66
+ fullname,
67
+ init_file,
68
+ loader=self,
69
+ submodule_search_locations=[candidate_path],
70
+ )
71
+ # Check for .py file
72
+ if os.path.isfile(candidate_path + ".py"):
73
+ return importlib.util.spec_from_file_location(
74
+ fullname, candidate_path + ".py", loader=self
75
+ )
51
76
  return None
52
77
 
53
78
  def create_module(
@@ -78,6 +103,7 @@ class JacMetaImporter(importlib.abc.MetaPathFinder, importlib.abc.Loader):
78
103
  target=target,
79
104
  base_path=base_path,
80
105
  override_name=module.__name__,
106
+ lng="py" if file_path.endswith(".py") else "jac",
81
107
  )
82
108
  if ret:
83
109
  loaded_module = ret[0]
@@ -27,7 +27,7 @@ node A {
27
27
  walker create_other_root {
28
28
  can enter with `root entry {
29
29
  other_root = `root().__jac__;
30
- _.save(other_root);
30
+ _jl.save(other_root);
31
31
  print(other_root.id);
32
32
  }
33
33
  }
@@ -44,7 +44,7 @@ walker create_custom_object {
44
44
 
45
45
  can exit1 with `root exit {
46
46
  # get directly from shelf
47
- o = _.get_context().mem.__shelf__.get(str(self.obj.__jac__.id)).archetype;
47
+ o = _jl.get_context().mem.__shelf__.get(str(self.obj.__jac__.id)).archetype;
48
48
  print(jid(o));
49
49
  print(o);
50
50
  }
@@ -87,6 +87,6 @@ walker delete_custom_object {
87
87
  has object_id: str;
88
88
 
89
89
  can enter1 with `root entry {
90
- _.destroy([&(self.object_id)]);
90
+ _jl.destroy([&(self.object_id)]);
91
91
  }
92
92
  }
jaclang/settings.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import configparser
4
4
  import os
5
+ from argparse import Namespace
5
6
  from dataclasses import dataclass, fields
6
7
 
7
8
 
@@ -19,11 +20,16 @@ class Settings:
19
20
  # Compiler configuration
20
21
  disable_mtllm: bool = False
21
22
  ignore_test_annex: bool = False
22
- pyout_jaclib_alias: str = "_"
23
+ pyout_jaclib_alias: str = "_jl"
24
+ pyfile_raise: bool = False
25
+ pyfile_raise_full: bool = False
23
26
 
24
27
  # Formatter configuration
25
28
  max_line_length: int = 88
26
29
 
30
+ # pytorch configuration
31
+ predynamo_pass: bool = False
32
+
27
33
  # LSP configuration
28
34
  lsp_debug: bool = False
29
35
 
@@ -42,6 +48,8 @@ class Settings:
42
48
  """Load settings from all available sources."""
43
49
  self.load_config_file()
44
50
  self.load_env_vars()
51
+ # CLI arguments are applied by the CLI after parsing, via
52
+ # `settings.load_command_line_arguments(args)` in start_cli.
45
53
 
46
54
  def load_config_file(self) -> None:
47
55
  """Load settings from a configuration file."""
@@ -64,19 +72,15 @@ class Settings:
64
72
  if env_value is not None:
65
73
  setattr(self, key, self.convert_type(env_value))
66
74
 
67
- # def load_command_line_arguments(self):
68
- # """Override settings from command-line arguments if provided."""
69
- # parser = argparse.ArgumentParser()
70
- # parser.add_argument(
71
- # "--debug",
72
- # type=self.str_to_bool,
73
- # nargs="?",
74
- # const=True,
75
- # default=self.config["debug"],
76
- # )
77
- # parser.add_argument("--port", type=int, default=self.config["port"])
78
- # parser.add_argument("--host", default=self.config["host"])
79
- # args = parser.parse_args()
75
+ def load_command_line_arguments(self, args: Namespace) -> None:
76
+ """Override settings from command-line arguments if provided."""
77
+ args_dict = vars(args) if not isinstance(args, dict) else args
78
+ for key in [f.name for f in fields(self)]:
79
+ if key in args_dict and args_dict[key] is not None:
80
+ val = args_dict[key]
81
+ if isinstance(val, str):
82
+ val = self.convert_type(val)
83
+ setattr(self, key, val)
80
84
 
81
85
  def str_to_bool(self, value: str) -> bool:
82
86
  """Convert string to boolean."""
@@ -63,15 +63,15 @@ with entry:__main__ {
63
63
  glob expected_area = 78.53981633974483;
64
64
 
65
65
  test calc_area {
66
- check almostEqual(calculate_area(RAD), expected_area);
66
+ assert almostEqual(calculate_area(RAD), expected_area);
67
67
  }
68
68
 
69
69
  test circle_area {
70
70
  c = Circle(RAD);
71
- check almostEqual(c.area(), expected_area);
71
+ assert almostEqual(c.area(), expected_area);
72
72
  }
73
73
 
74
74
  test circle_type {
75
75
  c = Circle(RAD);
76
- check c.shape_type == ShapeType.CIRCLE;
76
+ assert c.shape_type == ShapeType.CIRCLE;
77
77
  }
@@ -3,20 +3,20 @@ import from jaclang.runtimelib.machine { JacMachine }
3
3
 
4
4
  glob dynamic_module_source =
5
5
  """
6
- import from arch_create_util {UtilityNode}
6
+ import from arch_create_util {UtilityNode}
7
7
 
8
- walker DynamicWalker {
9
- can start with entry {
10
- print("DynamicWalker Started");
11
- here ++> UtilityNode(data=42);
12
- visit [-->](`?UtilityNode);
13
- }
8
+ walker DynamicWalker {
9
+ can start with entry {
10
+ print("DynamicWalker Started");
11
+ here ++> UtilityNode(data=42);
12
+ visit [-->](`?UtilityNode);
13
+ }
14
14
 
15
- def UtilityNode {
16
- here.display_data();
17
- }
18
- }
19
- """;
15
+ def UtilityNode {
16
+ here.display_data();
17
+ }
18
+ }
19
+ """;
20
20
 
21
21
 
22
22
  with entry {
@@ -1,14 +1,14 @@
1
1
  import re;
2
2
 
3
3
 
4
- glob a : int = 5;
4
+ glob a: int = 5;
5
5
 
6
6
 
7
7
  with entry {
8
8
  arguments = { x : None for x in re.findall(r'\{([A-Za-z0-9_]+)\}', "Apple {apple} pineapple {pineapple}") };
9
- a : int = 5;
9
+ a: int = 5;
10
10
  if False {
11
- with open(f"Apple{apple}.txt") as f {
11
+ with open(f"Apple{apple}.txt") as f {
12
12
  # Fix syntax highlighting
13
13
  print(
14
14
  f.read()
@@ -2,23 +2,23 @@ import from jaclang.runtimelib.machine { JacMachine }
2
2
  # Dynamically create a node archetype
3
3
  glob source_code =
4
4
  """
5
- node dynamic_node {
6
- has value:int;
7
- can print_value with entry {
8
- print("Dynamic Node Value:", f'{self.value}');
9
- }
10
- }
11
- """;
5
+ node dynamic_node {
6
+ has value:int;
7
+ can print_value with entry {
8
+ print("Dynamic Node Value:", f'{self.value}');
9
+ }
10
+ }
11
+ """;
12
12
 
13
13
  # Create a new walker archetype dynamically
14
14
  glob walker_code =
15
15
  """
16
- walker dynamic_walker {
17
- can visit_nodes with entry {
18
- visit [-->];
19
- }
20
- }
21
- """;
16
+ walker dynamic_walker {
17
+ can visit_nodes with entry {
18
+ visit [-->];
19
+ }
20
+ }
21
+ """;
22
22
 
23
23
 
24
24
  with entry {
@@ -0,0 +1,18 @@
1
+ from jaclang.tests.fixtures.jac_run_py_import import MyModule
2
+
3
+ class SimpleClass:
4
+ def __init__(self, name: str, age: int) -> None:
5
+ self.name = name
6
+ self.age = age
7
+
8
+ def greet(self):
9
+ return f"Hello, my name is {self.name} and I am {self.age} years old."
10
+
11
+
12
+ # Create an object of the class
13
+ person = SimpleClass("Alice", 30)
14
+
15
+ # Run the greet method
16
+ print(person.greet())
17
+
18
+ MyModule.init()
@@ -0,0 +1,13 @@
1
+ class MyModule:
2
+ @staticmethod
3
+ def init():
4
+ print("MyModule initialized!")
5
+
6
+ @staticmethod
7
+ def do_something():
8
+ print("Doing something...")
9
+
10
+
11
+
12
+
13
+
@@ -0,0 +1,15 @@
1
+
2
+
3
+ with entry {
4
+ x = lambda a: int, b: int : b + a;
5
+ print(x(5, 4));
6
+ y = lambda : 567;
7
+ print(y());
8
+ }
9
+
10
+
11
+ with entry{
12
+ f = lambda x:Any :"even" if x % 2 == 0 else "odd";
13
+ print(f(7));
14
+
15
+ }
@@ -0,0 +1,18 @@
1
+
2
+
3
+ walker Tourist {
4
+ can travel with City entry {
5
+
6
+ def foo(a:int){}
7
+ x = lambda a: int, b: int : b + a;
8
+ y = lambda : 567;
9
+ sorted_users = sorted(
10
+ users,
11
+ key=lambda x: dict: x["email"], reverse=True
12
+ );
13
+
14
+ }
15
+ def visit_city(c:City){
16
+ print("Visiting", c.name);
17
+ }
18
+ }
@@ -1,17 +1,17 @@
1
1
  glob x = 5, y = 2;
2
2
 
3
3
  test a {
4
- check almostEqual(5, x);
4
+ assert almostEqual(5, x);
5
5
  }
6
6
 
7
7
  test b {
8
- check "l" in "llm";
8
+ assert "l" in "llm";
9
9
  }
10
10
 
11
11
  test c {
12
- check x - y == 3;
12
+ assert x - y == 3;
13
13
  }
14
14
 
15
15
  test d {
16
- check 1 == 2;
16
+ assert 1 == 2;
17
17
  }
@@ -0,0 +1,9 @@
1
+
2
+
3
+ def foo(a:int,/,/){}
4
+ def foo(a:int,/,*,*){}
5
+ def foo(a:int,/,*,**kwargs:int,*){}
6
+ def foo(a:int,/,*,**kwargs:int,*){}
7
+ def foo(a:int,/,*,**kwargs:int,/){}
8
+ def foo(a:int,/,*,**kwargs:int,/){}
9
+ def foo(a:int,/,*,/,**kwargs:int,/){}
@@ -0,0 +1,42 @@
1
+ # Test complex parameter combinations
2
+
3
+ def ultimate_signature(
4
+ pos_only: int,
5
+ pos_def: str = "def",
6
+ /,
7
+ reg_def: bool = True,
8
+ *args: int,
9
+ kw_req: str,
10
+ kw_opt: int = 100,
11
+ **kwargs: any
12
+ ) -> str {
13
+ return f"{pos_only}|{pos_def}|{reg_def}|{len(args)}|{kw_req}|{kw_opt}|{len(kwargs)}";
14
+ }
15
+
16
+ def separators_only(/, *, x: int) -> int {
17
+ return x;
18
+ }
19
+
20
+ def edge_case_mix(a: int, /, b: str, *args: float, c: bool, **kwargs: str) -> str {
21
+ return f"{a}-{b}-{len(args)}-{c}-{len(kwargs)}";
22
+ }
23
+
24
+ def recursive_test(data: int, /, depth: int = 0, *, max_depth: int = 2) -> int {
25
+ if depth >= max_depth {
26
+ return data;
27
+ }
28
+ return recursive_test(data + 1, depth + 1, max_depth=max_depth);
29
+ }
30
+
31
+ def validation_test(x: int, y: str = "test", /, z: float, *args: int, w: bool, **kwargs: str) -> str {
32
+ return f"x:{x},y:{y},z:{z},args:{len(args)},w:{w},kwargs:{len(kwargs)}";
33
+ }
34
+
35
+ with entry {
36
+ print("ULTIMATE_MIN:", ultimate_signature(1, reg_def=2.5, kw_req="test"));
37
+ print("ULTIMATE_FULL:", ultimate_signature(1, "custom", 3.14, False, 10, 20, kw_req="req", kw_opt=200, extra="data"));
38
+ print("SEPARATORS:", separators_only(x=42));
39
+ print("EDGE_MIX:", edge_case_mix(1, "test", 1.1, 2.2, c=True, name="edge"));
40
+ print("RECURSIVE:", recursive_test(5), recursive_test(10, max_depth=1));
41
+ print("VALIDATION:", validation_test(1, 2.5, 10, 20, w=True, debug="on"));
42
+ }
@@ -0,0 +1,207 @@
1
+ # Test failing keyword-only parameter cases
2
+
3
+ def strict_kwonly(*, x: int, y: str) -> str {
4
+ return f"{x}: {y}";
5
+ }
6
+
7
+ def kwonly_with_defaults(*, req: int, opt: str = "default") -> str {
8
+ return f"{req}-{opt}";
9
+ }
10
+
11
+ def mixed_kwonly(a: int, *, kw1: str, kw2: bool = False) -> dict {
12
+ return {"a": a, "kw1": kw1, "kw2": kw2};
13
+ }
14
+
15
+ def complex_kwonly(pos: int, /, reg: str, *args: float, kw_req: bool, kw_opt: int = 10, **kwargs: any) -> dict {
16
+ return {"pos": pos, "reg": reg, "args": list(args), "kw_req": kw_req, "kw_opt": kw_opt, "kwargs": kwargs};
17
+ }
18
+
19
+ def only_star_separator(*, x: int) -> int {
20
+ return x * 2;
21
+ }
22
+
23
+ def nested_kwonly_issues(*, outer: dict, inner: list = []) -> int {
24
+ return len(outer) + len(inner);
25
+ }
26
+
27
+ with entry {
28
+ print("=== TESTING KEYWORD-ONLY FAILURES ===");
29
+
30
+ # Test 1: Calling kw-only with positional arguments
31
+ try {
32
+ result = strict_kwonly(42, "test");
33
+ print("❌ FAIL: Should reject positional for kw-only");
34
+ } except Exception as e {
35
+ print("✅ PASS: Caught kw-only positional error:", type(e).__name__);
36
+ }
37
+
38
+ # Test 2: Missing required keyword-only argument
39
+ try {
40
+ result = strict_kwonly(x=42);
41
+ print("❌ FAIL: Should reject missing required kw-only");
42
+ } except Exception as e {
43
+ print("✅ PASS: Caught missing kw-only error:", type(e).__name__);
44
+ }
45
+
46
+ # Test 3: Missing required in defaults mix
47
+ try {
48
+ result = kwonly_with_defaults(opt="custom");
49
+ print("❌ FAIL: Should reject missing required kw-only");
50
+ } except Exception as e {
51
+ print("✅ PASS: Caught missing required in defaults:", type(e).__name__);
52
+ }
53
+
54
+ # Test 4: Extra unknown keyword arguments (without **kwargs)
55
+ try {
56
+ result = strict_kwonly(x=42, y="test", extra="unknown");
57
+ print("❌ FAIL: Should reject unknown keyword");
58
+ } except Exception as e {
59
+ print("✅ PASS: Caught unknown keyword error:", type(e).__name__);
60
+ }
61
+
62
+ # Test 5: Mixed signature - wrong positional count
63
+ try {
64
+ result = mixed_kwonly(1, 2, kw1="test");
65
+ print("❌ FAIL: Should reject too many positional");
66
+ } except Exception as e {
67
+ print("✅ PASS: Caught too many positional error:", type(e).__name__);
68
+ }
69
+
70
+ # Test 6: Mixed signature - missing kw-only required
71
+ try {
72
+ result = mixed_kwonly(42);
73
+ print("❌ FAIL: Should reject missing kw-only in mixed");
74
+ } except Exception as e {
75
+ print("✅ PASS: Caught missing kw-only in mixed:", type(e).__name__);
76
+ }
77
+
78
+ # Test 7: Complex signature - kw-only as positional
79
+ try {
80
+ result = complex_kwonly(1, "reg", 1.5, True);
81
+ print("❌ FAIL: Should reject kw-only as positional");
82
+ } except Exception as e {
83
+ print("✅ PASS: Caught kw-only as positional error:", type(e).__name__);
84
+ }
85
+
86
+ # Test 8: Complex signature - missing required kw-only
87
+ try {
88
+ result = complex_kwonly(1, "reg", 1.5, 2.5);
89
+ print("❌ FAIL: Should reject missing kw-only in complex");
90
+ } except Exception as e {
91
+ print("✅ PASS: Caught missing kw-only in complex:", type(e).__name__);
92
+ }
93
+
94
+ # Test 9: Only star separator - positional call
95
+ try {
96
+ result = only_star_separator(42);
97
+ print("❌ FAIL: Should reject positional for star-only");
98
+ } except Exception as e {
99
+ print("✅ PASS: Caught star-only positional error:", type(e).__name__);
100
+ }
101
+
102
+
103
+ # Test 11: Mutable default issues
104
+ try {
105
+ result1 = nested_kwonly_issues(outer={"a": 1});
106
+ result2 = nested_kwonly_issues(outer={"b": 2});
107
+ print("✅ INFO: Mutable defaults - result1:", result1, "result2:", result2);
108
+ } except Exception as e {
109
+ print("❌ FAIL: Mutable defaults caused error:", type(e).__name__);
110
+ }
111
+
112
+ # Test 12: Valid calls that should work
113
+ try {
114
+ result = strict_kwonly(x=42, y="valid");
115
+ print("✅ PASS: Valid kw-only call:", result);
116
+ } except Exception as e {
117
+ print("❌ FAIL: Valid kw-only call rejected:", type(e).__name__);
118
+ }
119
+
120
+ try {
121
+ result = kwonly_with_defaults(req=100);
122
+ print("✅ PASS: Valid kw-only with defaults:", result);
123
+ } except Exception as e {
124
+ print("❌ FAIL: Valid defaults call rejected:", type(e).__name__);
125
+ }
126
+
127
+ try {
128
+ result = mixed_kwonly(5, kw1="test");
129
+ print("✅ PASS: Valid mixed kw-only:", result);
130
+ } except Exception as e {
131
+ print("❌ FAIL: Valid mixed call rejected:", type(e).__name__);
132
+ }
133
+ }
134
+
135
+ def additional_kwonly_edge_cases() -> None {
136
+ print("\n=== ADDITIONAL KEYWORD-ONLY EDGE CASES ===");
137
+
138
+ # Test 14: Keyword argument name conflicts with Python keywords
139
+ def python_keyword_conflicts(*, class_name: str, import_path: str = "default") -> str {
140
+ return f"{class_name}: {import_path}";
141
+ }
142
+
143
+ try {
144
+ result = python_keyword_conflicts(class_name="TestClass");
145
+ print("✅ PASS: Python keyword as param name:", result);
146
+ } except Exception as e {
147
+ print("❌ FAIL: Python keyword param failed:", type(e).__name__);
148
+ }
149
+
150
+ # Test 15: Very long keyword-only parameter lists
151
+ def many_kwonly_params(
152
+ *,
153
+ p1: int, p2: int, p3: int, p4: int, p5: int,
154
+ p6: str = "d6", p7: str = "d7", p8: str = "d8"
155
+ ) -> dict {
156
+ return {"sum": p1+p2+p3+p4+p5, "strings": [p6, p7, p8]};
157
+ }
158
+
159
+ try {
160
+ result = many_kwonly_params(p1=1, p2=2, p3=3, p4=4, p5=5);
161
+ print("✅ PASS: Many kw-only params:", result);
162
+ } except Exception as e {
163
+ print("❌ FAIL: Many kw-only params failed:", type(e).__name__);
164
+ }
165
+
166
+ # Test 16: Nested function calls as keyword arguments
167
+ def helper_func() -> str {
168
+ return "helper_result";
169
+ }
170
+
171
+ def kwonly_with_function_calls(*, data: str, processed: bool = True) -> str {
172
+ return f"data={data}, processed={processed}";
173
+ }
174
+
175
+ try {
176
+ result = kwonly_with_function_calls(data=helper_func(), processed=False);
177
+ print("✅ PASS: Function calls as kw args:", result);
178
+ } except Exception as e {
179
+ print("❌ FAIL: Function calls as kw args failed:", type(e).__name__);
180
+ }
181
+
182
+ # Test 17: Empty string and special values
183
+ try {
184
+ result = strict_kwonly(x=0, y="");
185
+ print("✅ PASS: Edge values (0, empty string):", result);
186
+ } except Exception as e {
187
+ print("❌ FAIL: Edge values failed:", type(e).__name__);
188
+ }
189
+
190
+ # Test 18: Really test duplicate keyword detection properly
191
+ # Note: This might not be detectable at runtime in some languages
192
+ def test_duplicate_detection() -> None {
193
+ try {
194
+ # This should be a syntax error, but let's see what happens
195
+ call_string = "strict_kwonly(x=42, y='test', x=99)";
196
+ print("INFO: Would test duplicate keywords, but this is syntax-level");
197
+ } except Exception as e {
198
+ print("✅ PASS: Duplicate detection works:", type(e).__name__);
199
+ }
200
+ }
201
+
202
+ test_duplicate_detection();
203
+ }
204
+
205
+ with entry {
206
+ additional_kwonly_edge_cases();
207
+ }