jaclang 0.8.9__py3-none-any.whl → 0.8.10__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.py +147 -25
  2. jaclang/cli/cmdreg.py +144 -8
  3. jaclang/compiler/__init__.py +6 -1
  4. jaclang/compiler/codeinfo.py +16 -1
  5. jaclang/compiler/constant.py +33 -13
  6. jaclang/compiler/jac.lark +130 -31
  7. jaclang/compiler/larkparse/jac_parser.py +2 -2
  8. jaclang/compiler/parser.py +567 -176
  9. jaclang/compiler/passes/__init__.py +2 -1
  10. jaclang/compiler/passes/ast_gen/__init__.py +5 -0
  11. jaclang/compiler/passes/ast_gen/base_ast_gen_pass.py +54 -0
  12. jaclang/compiler/passes/ast_gen/jsx_processor.py +344 -0
  13. jaclang/compiler/passes/ecmascript/__init__.py +25 -0
  14. jaclang/compiler/passes/ecmascript/es_unparse.py +576 -0
  15. jaclang/compiler/passes/ecmascript/esast_gen_pass.py +2068 -0
  16. jaclang/compiler/passes/ecmascript/estree.py +972 -0
  17. jaclang/compiler/passes/ecmascript/tests/__init__.py +1 -0
  18. jaclang/compiler/passes/ecmascript/tests/fixtures/advanced_language_features.jac +170 -0
  19. jaclang/compiler/passes/ecmascript/tests/fixtures/class_separate_impl.impl.jac +30 -0
  20. jaclang/compiler/passes/ecmascript/tests/fixtures/class_separate_impl.jac +14 -0
  21. jaclang/compiler/passes/ecmascript/tests/fixtures/client_jsx.jac +89 -0
  22. jaclang/compiler/passes/ecmascript/tests/fixtures/core_language_features.jac +195 -0
  23. jaclang/compiler/passes/ecmascript/tests/test_esast_gen_pass.py +167 -0
  24. jaclang/compiler/passes/ecmascript/tests/test_js_generation.py +239 -0
  25. jaclang/compiler/passes/main/__init__.py +0 -3
  26. jaclang/compiler/passes/main/annex_pass.py +23 -1
  27. jaclang/compiler/passes/main/pyast_gen_pass.py +324 -234
  28. jaclang/compiler/passes/main/pyast_load_pass.py +46 -11
  29. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +2 -0
  30. jaclang/compiler/passes/main/sym_tab_build_pass.py +18 -1
  31. jaclang/compiler/passes/main/tests/fixtures/autoimpl.cl.jac +7 -0
  32. jaclang/compiler/passes/main/tests/fixtures/checker_arity.jac +3 -0
  33. jaclang/compiler/passes/main/tests/fixtures/checker_class_construct.jac +33 -0
  34. jaclang/compiler/passes/main/tests/fixtures/defuse_modpath.jac +7 -0
  35. jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +2 -1
  36. jaclang/compiler/passes/main/tests/test_checker_pass.py +31 -2
  37. jaclang/compiler/passes/main/tests/test_def_use_pass.py +12 -0
  38. jaclang/compiler/passes/main/tests/test_import_pass.py +23 -4
  39. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +25 -0
  40. jaclang/compiler/passes/main/type_checker_pass.py +7 -0
  41. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +115 -0
  42. jaclang/compiler/passes/tool/fuse_comments_pass.py +1 -10
  43. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +4 -1
  44. jaclang/compiler/passes/transform.py +9 -1
  45. jaclang/compiler/passes/uni_pass.py +5 -7
  46. jaclang/compiler/program.py +22 -25
  47. jaclang/compiler/tests/test_client_codegen.py +113 -0
  48. jaclang/compiler/tests/test_importer.py +12 -10
  49. jaclang/compiler/tests/test_parser.py +249 -3
  50. jaclang/compiler/type_system/type_evaluator.jac +169 -50
  51. jaclang/compiler/type_system/type_utils.py +1 -1
  52. jaclang/compiler/type_system/types.py +6 -0
  53. jaclang/compiler/unitree.py +430 -84
  54. jaclang/langserve/engine.jac +224 -288
  55. jaclang/langserve/sem_manager.jac +12 -8
  56. jaclang/langserve/server.jac +48 -48
  57. jaclang/langserve/tests/fixtures/greet.py +17 -0
  58. jaclang/langserve/tests/fixtures/md_path.jac +22 -0
  59. jaclang/langserve/tests/fixtures/user.jac +15 -0
  60. jaclang/langserve/tests/test_server.py +66 -371
  61. jaclang/lib.py +1 -1
  62. jaclang/runtimelib/client_bundle.py +169 -0
  63. jaclang/runtimelib/client_runtime.jac +586 -0
  64. jaclang/runtimelib/constructs.py +2 -0
  65. jaclang/runtimelib/machine.py +259 -100
  66. jaclang/runtimelib/meta_importer.py +111 -22
  67. jaclang/runtimelib/mtp.py +15 -0
  68. jaclang/runtimelib/server.py +1089 -0
  69. jaclang/runtimelib/tests/fixtures/client_app.jac +18 -0
  70. jaclang/runtimelib/tests/fixtures/custom_access_validation.jac +1 -1
  71. jaclang/runtimelib/tests/fixtures/savable_object.jac +4 -5
  72. jaclang/runtimelib/tests/fixtures/serve_api.jac +75 -0
  73. jaclang/runtimelib/tests/test_client_bundle.py +55 -0
  74. jaclang/runtimelib/tests/test_client_render.py +63 -0
  75. jaclang/runtimelib/tests/test_serve.py +1069 -0
  76. jaclang/settings.py +0 -2
  77. jaclang/tests/fixtures/iife_functions.jac +142 -0
  78. jaclang/tests/fixtures/iife_functions_client.jac +143 -0
  79. jaclang/tests/fixtures/multistatement_lambda.jac +116 -0
  80. jaclang/tests/fixtures/multistatement_lambda_client.jac +113 -0
  81. jaclang/tests/fixtures/needs_import_dup.jac +6 -4
  82. jaclang/tests/fixtures/py_run.py +7 -5
  83. jaclang/tests/fixtures/pyfunc_fstr.py +2 -2
  84. jaclang/tests/fixtures/simple_lambda_test.jac +12 -0
  85. jaclang/tests/test_cli.py +1 -1
  86. jaclang/tests/test_language.py +10 -39
  87. jaclang/tests/test_reference.py +17 -2
  88. jaclang/utils/NonGPT.py +375 -0
  89. jaclang/utils/helpers.py +44 -16
  90. jaclang/utils/lang_tools.py +31 -4
  91. jaclang/utils/tests/test_lang_tools.py +1 -1
  92. jaclang/utils/treeprinter.py +8 -3
  93. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/METADATA +3 -3
  94. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/RECORD +96 -66
  95. jaclang/compiler/passes/main/binder_pass.py +0 -594
  96. jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +0 -47
  97. jaclang/compiler/passes/main/tests/test_binder_pass.py +0 -111
  98. jaclang/langserve/tests/session.jac +0 -294
  99. jaclang/langserve/tests/test_dev_server.py +0 -80
  100. jaclang/runtimelib/importer.py +0 -351
  101. jaclang/tests/test_typecheck.py +0 -542
  102. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/WHEEL +0 -0
  103. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/entry_points.txt +0 -0
@@ -2,7 +2,10 @@
2
2
 
3
3
  import asyncio;
4
4
  import logging;
5
+ import os;
6
+ import sys;
5
7
  import time;
8
+ import threading;
6
9
  import from concurrent.futures { ThreadPoolExecutor }
7
10
  import from typing { Callable, Optional }
8
11
 
@@ -11,14 +14,19 @@ import from jaclang { JacMachineInterface as Jac }
11
14
  import from jaclang.compiler.constant { SymbolType }
12
15
  import from jaclang.compiler.program { JacProgram }
13
16
  import from jaclang.compiler.type_system.type_utils { get_completion_items }
14
- import from jaclang.compiler.type_system.types { ClassType, TypeBase }
17
+ import from jaclang.compiler.type_system.types {
18
+ ClassType,
19
+ FunctionType,
20
+ ModuleType,
21
+ TypeBase
22
+ }
15
23
  import from jaclang.compiler.unitree { UniScopeNode }
16
- import from sem_manager { SemTokManager }
24
+ import from .sem_manager { SemTokManager }
17
25
  import from jaclang.vendor.pygls { uris }
18
26
  import from jaclang.vendor.pygls.server { LanguageServer }
19
27
 
20
28
  import lsprotocol.types as lspt;
21
- import utils;
29
+ import from . { utils }
22
30
 
23
31
 
24
32
  """Handles Jac module, semantic manager, and alert management."""
@@ -49,10 +57,16 @@ class ModuleManager {
49
57
 
50
58
  """Remove errors and warnings for a specific file from the lists."""
51
59
  def clear_alerts_for_file(self: ModuleManager, file_path_fs: str) -> None {
52
- self.program.errors_had =
53
- [ e for e in self.program.errors_had if e.loc.mod_path != file_path_fs ];
54
- self.program.warnings_had =
55
- [ w for w in self.program.warnings_had if w.loc.mod_path != file_path_fs ];
60
+ self.program.errors_had = [
61
+ e
62
+ for e in self.program.errors_had
63
+ if e.loc.mod_path != file_path_fs
64
+ ];
65
+ self.program.warnings_had = [
66
+ w
67
+ for w in self.program.warnings_had
68
+ if w.loc.mod_path != file_path_fs
69
+ ];
56
70
  }
57
71
  }
58
72
 
@@ -63,22 +77,28 @@ class JacLangServer(JacProgram , LanguageServer) {
63
77
  def init(self: JacLangServer) -> None {
64
78
  LanguageServer.init(self, 'jac-lsp', 'v0.1');
65
79
  JacProgram.init(self);
66
- self.executor = ThreadPoolExecutor();
67
- self.tasks: dict[(str, asyncio.Task)] = {};
68
80
  self.sem_managers: dict[(str, SemTokManager)] = {};
69
81
  self.module_manager = ModuleManager(self, self.sem_managers);
70
- self._quick_tasks: dict[str, asyncio.Task] = {};
71
- self._deep_tasks: dict[str, asyncio.Task] = {};
82
+
83
+ # Threading support - per file thread with cancellation token
84
+ self.type_check_threads: dict[str, threading.Thread] = {};
85
+ self.cancel_tokens: dict[str, threading.Event] = {};
86
+
87
+ self.debounce_timers: dict[str, threading.Timer] = {};
88
+
89
+ # Disable logging library from logging anything
90
+ # logging.getLogger().setLevel(logging.CRITICAL);
72
91
  }
73
92
 
74
93
  """Return diagnostics for all files as a dict {uri: diagnostics}."""
75
- @ property
76
- def diagnostics(self: JacLangServer, ) -> dict[str, list] {
94
+ @property
95
+ def diagnostics(self: JacLangServer,) -> dict[str, list] {
77
96
  result = {};
78
97
  for file_path in self.mod.hub {
79
98
  uri = uris.from_fs_path(file_path);
80
- result[uri] =
81
- utils.gen_diagnostics(file_path, self.errors_had, self.warnings_had);
99
+ result[uri] = utils.gen_diagnostics(
100
+ file_path, self.errors_had, self.warnings_had
101
+ );
82
102
  }
83
103
  return result;
84
104
  }
@@ -95,114 +115,112 @@ class JacLangServer(JacProgram , LanguageServer) {
95
115
 
96
116
  """Update modules in JacProgram's hub and semantic managers."""
97
117
  def update_modules(
98
- self: JacLangServer,
99
- file_path: str,
100
- build: uni.Module,
101
- need: bool = True
118
+ self: JacLangServer, file_path: str, build: uni.Module, need: bool = True
102
119
  ) -> None {
103
120
  self.log_py(f"'Updating modules for '{file_path}");
104
121
  self.module_manager.update(file_path, build, update_annexed=need);
105
122
  }
106
123
 
107
- """Rebuild a file (syntax only)."""
108
- def quick_check(self: JacLangServer, file_path: str) -> bool {
109
- try {
110
- document = self.workspace.get_text_document(file_path);
111
- fs_path = document.path;
112
-
113
- self._clear_alerts_for_file(fs_path);
114
- build = self.compile(use_str=document.source, file_path=fs_path);
115
- self.update_modules(fs_path, build, need=False);
116
- # to display diagnostics, it needs URI starts with "file://"
117
- self.publish_diagnostics(
118
- file_path,
119
- utils.gen_diagnostics(fs_path, self.errors_had, self.warnings_had)
120
- );
121
- build_errors =
122
- [ e for e in self.errors_had if e.loc.mod_path == fs_path];
123
- return len(build_errors) == 0;
124
- } except Exception as e {
125
- self.log_error(f"'Error during syntax check: '{e}");
126
- return False;
127
- }
128
- }
129
-
130
124
  """Rebuild a file and its dependencies (typecheck)."""
131
- def deep_check(
132
- self: JacLangServer,
133
- file_path: str,
134
- annex_view: Optional[str] = None
125
+ def type_check_file(
126
+ self: JacLangServer, file_path: str,cancel_token: Event | None = None, annex_view: Optional[str] = None
135
127
  ) -> bool {
136
128
  try {
137
129
  start_time = time.time();
138
130
  document = self.workspace.get_text_document(file_path);
139
131
  fs_path = document.path;
140
-
141
132
  self._clear_alerts_for_file(fs_path);
142
- build = self.compile(use_str=document.source, file_path=document.path,type_check=True);
133
+
134
+ t = time.time();
135
+ f = file_path.split("/")[-1].split("\\")[-1];
136
+ self.debug("Type checking started for " + f + " t=" + str(t));
137
+
138
+ build = self.compile(
139
+ use_str=document.source, file_path=document.path, type_check=True
140
+ );
141
+ self.debug(f" 'Type checking for '{f}' done {str(time.time())} in '{(time.time() - t)}' seconds.'");
142
+ if cancel_token and cancel_token.is_set(){
143
+ self.debug(" ** Type checking canceled for " + f + " t=" + str(time.time()));
144
+ self.debug(f" 'Type checking for '{f}' was canceled in '{(time.time() - t)}' seconds.'");
145
+ return;
146
+ }
147
+
143
148
  self.update_modules(fs_path, build);
144
149
  if build.annexable_by {
145
- return self.deep_check(
146
- uris.from_fs_path(build.annexable_by),
147
- annex_view=fs_path
150
+ return self.type_check_file(
151
+ uris.from_fs_path(build.annexable_by), cancel_token=cancel_token, annex_view=fs_path
148
152
  );
149
153
  }
150
154
  self.publish_diagnostics(
151
- # to display diagnostic , it need URI starts with "file://"
152
- uris.from_fs_path(annex_view) if annex_view else uris.from_fs_path(fs_path) ,
155
+ (
156
+ # to display diagnostic , it need URI starts with "file://"
157
+ uris.from_fs_path(
158
+ annex_view
159
+ )
160
+ if annex_view
161
+ else uris.from_fs_path(fs_path)
162
+ ),
153
163
  utils.gen_diagnostics(
154
- annex_view if annex_view else fs_path,
164
+ (annex_view if annex_view else fs_path),
155
165
  self.errors_had,
156
166
  self.warnings_had
157
167
  )
158
168
  );
159
169
  if annex_view {
160
170
  self.publish_diagnostics(
161
- uris.from_fs_path(fs_path) ,
171
+ uris.from_fs_path(fs_path),
162
172
  utils.gen_diagnostics(fs_path, self.errors_had, self.warnings_had)
163
173
  );
164
174
  }
165
175
  self.log_py(
166
176
  f"'PROFILE: Deep check took '{(time.time() - start_time)}' seconds.'"
167
177
  );
168
- return len(self.errors_had) == 0;
169
178
  } except Exception as e {
170
- self.log_py(f"'Error during deep check: '{e}");
171
- return False;
179
+ if cancel_token and not cancel_token.is_set() {
180
+ self.log_py(f"'Error during type check: '{e}");
181
+ }
172
182
  }
173
183
  }
174
184
 
175
- """Analyze and publish diagnostics with debouncing."""
176
- async def launch_quick_check(self: JacLangServer, uri: str, delay: float = 0.5) -> bool {
177
- try {
178
- return await self._debounced_run(self.quick_check, uri, delay, self._quick_tasks);
179
- } except asyncio.CancelledError {
180
- return False;
181
- } except Exception as e {
182
- self.log_error(f" 'Error during quick check launch: ' {e} ");
183
- return False;
185
+ """Type check with thread cancellation and proper debouncing."""
186
+ async def type_check(self: JacLangServer, file_path: str) -> None {
187
+ # Cancel existing debounce timer for this file
188
+ if file_path in self.debounce_timers {
189
+ self.debounce_timers[file_path].cancel();
184
190
  }
185
- }
186
- """Analyze and publish diagnostics with debouncing."""
187
- async def launch_deep_check(
188
- self: JacLangServer,
189
- uri: str,
190
- delay: float = 1.0,
191
- annex_view: Optional[str] = None
192
- ) -> bool {
193
- def deep_check_wrapper(file_uri: str) -> bool {
194
- return self.deep_check(file_uri, annex_view);
191
+
192
+ # Cancel existing type check thread for this file if running
193
+ if file_path in self.cancel_tokens {
194
+ self.cancel_tokens[file_path].set();
195
195
  }
196
- try {
197
- return await self._debounced_run(deep_check_wrapper, uri, delay, self._deep_tasks);
198
- } except asyncio.CancelledError {
199
- return False;
200
- } except Exception as e {
201
- self.log_error(f" 'Error during deep check launch: ' {e} ");
196
+
197
+ # Create new cancellation token for the upcoming type check
198
+ self.cancel_tokens[file_path] = threading.Event();
199
+
200
+ # Create a debounced function that will run the actual type check
201
+ def debounced_type_check() -> None {
202
+ # Start new thread for type checking
203
+ print(" debounced_type_check ->", time.time(), file=sys.stderr);
204
+ self.type_check_threads[file_path] = threading.Thread(
205
+ target=self.type_check_file,
206
+ args=(file_path, self.cancel_tokens[file_path])
207
+ );
208
+ self.type_check_threads[file_path].start();
209
+ self.type_check_threads[file_path].join();
202
210
  }
211
+
212
+ # Set up debounce timer with the actual type check function
213
+ self.debounce_timers[file_path] = threading.Timer(0.5, debounced_type_check);
214
+ self.debounce_timers[file_path].start();
215
+
216
+ # Wait for the current type check thread to finish (if any)
217
+ await asyncio.to_thread(self.debounce_timers[file_path].join);
218
+
203
219
  }
204
220
 
205
- def get_token_at_position(self: JacLangServer, file_path: str, position: lspt.Position) -> Optional[uni.AstNode] {
221
+ def get_token_at_position(
222
+ self: JacLangServer, file_path: str, position: lspt.Position
223
+ ) -> Optional[uni.AstNode] {
206
224
  fs_path = uris.to_fs_path(file_path);
207
225
  if fs_path not in self.mod.hub {
208
226
  return None;
@@ -211,8 +229,9 @@ class JacLangServer(JacProgram , LanguageServer) {
211
229
  if not sem_mgr {
212
230
  return None;
213
231
  }
214
- token_index =
215
- utils.find_index(sem_mgr.sem_tokens, position.line, position.character);
232
+ token_index = utils.find_index(
233
+ sem_mgr.sem_tokens, position.line, position.character
234
+ );
216
235
  if token_index is None {
217
236
  return None;
218
237
  }
@@ -221,9 +240,8 @@ class JacLangServer(JacProgram , LanguageServer) {
221
240
  }
222
241
 
223
242
  def debug(self: JacLangServer, msg: str) -> None {
224
- self.log_py("[DEBUG] " + ("-" * 80));
243
+ print(f"[DEBUG] {msg}", file=sys.stderr);
225
244
  self.log_py(f"[DEBUG] {msg}");
226
- self.log_py("[DEBUG] " + ("-" * 80));
227
245
  }
228
246
 
229
247
  """Return completion for a file."""
@@ -234,15 +252,18 @@ class JacLangServer(JacProgram , LanguageServer) {
234
252
  completion_trigger: Optional[str]
235
253
  ) -> lspt.CompletionList {
236
254
  self.debug("getting completion for " + file_uri + " at " + str(position));
237
-
238
255
  try {
239
256
  file_path = uris.to_fs_path(file_uri);
240
-
241
- if (node_at_pos := self.get_node_at_position(file_path, position.line, position.character - 1)) {
242
- self.debug("found the node at pos " + str(position) + " " + str(node_at_pos));
243
-
257
+ if (
258
+ node_at_pos := self.get_node_at_position(
259
+ file_path, position.line, position.character - 1
260
+ )
261
+ ) {
262
+ self.debug(
263
+ "found the node at pos " + str(position) + " " + str(node_at_pos)
264
+ );
244
265
  # For each trigger character we need to handle the completion differently
245
- if isinstance(node_at_pos, uni.Token) {
266
+ if isinstance(node_at_pos, uni.Token) {
246
267
  if node_at_pos.name == "DOT" {
247
268
  member_access = node_at_pos.parent;
248
269
  self.debug("found dot " + str(member_access));
@@ -250,32 +271,35 @@ class JacLangServer(JacProgram , LanguageServer) {
250
271
  self.debug("before returning the list");
251
272
  return self.get_completion_of_node(member_access.target);
252
273
  }
253
-
254
274
  # FIXME: This is wrong but imma do it anyways like this for now.
255
- } elif node_at_pos.name == "NAME" {
256
-
275
+ } elif node_at_pos.name == "NAME" {
257
276
  # Name of atom trailer.
258
- if node_at_pos.parent and isinstance(node_at_pos.parent, uni.AtomTrailer) {
259
- self.debug("found name in atom trailer " + str(node_at_pos.parent));
260
- return self.get_completion_of_node(node_at_pos.parent.target);
277
+ if (
278
+ node_at_pos.parent
279
+ and isinstance(node_at_pos.parent, uni.AtomTrailer)
280
+ ) {
281
+ self.debug(
282
+ "found name in atom trailer " + str(node_at_pos.parent)
283
+ );
284
+ return self.get_completion_of_node(
285
+ node_at_pos.parent.target
286
+ );
261
287
  }
262
-
263
288
  # Just a name field.
264
- if scope_node := node_at_pos.find_parent_of_type(uni.UniScopeNode) {
289
+ if scope_node := node_at_pos.find_parent_of_type(
290
+ uni.UniScopeNode
291
+ ) {
265
292
  self.debug("found name in scope node " + str(scope_node));
266
293
  return self.get_completion_of_node(scope_node);
267
294
  }
268
295
  }
269
296
  }
270
297
  }
271
-
272
298
  return lspt.CompletionList(is_incomplete=False, items=[]);
273
-
274
299
  } except Exception as e {
275
300
  self.log_py(f"'Error during completion: '{e}");
276
301
  return lspt.CompletionList(is_incomplete=False, items=[]);
277
302
  }
278
-
279
303
  self.debug("returning empty list");
280
304
  }
281
305
 
@@ -286,7 +310,9 @@ class JacLangServer(JacProgram , LanguageServer) {
286
310
  return None;
287
311
  }
288
312
 
289
- def get_node_at_position(self: JacLangServer, file_path: str, line:int, col: int) -> Optional[uni.AstNode] {
313
+ def get_node_at_position(
314
+ self: JacLangServer, file_path: str, line: int, col: int
315
+ ) -> Optional[uni.AstNode] {
290
316
  if (ast := self.get_ast_of_file(file_path)) {
291
317
  for ast_node in ast._in_mod_nodes {
292
318
  if not isinstance(ast_node, uni.Token) {
@@ -300,7 +326,9 @@ class JacLangServer(JacProgram , LanguageServer) {
300
326
  return None;
301
327
  }
302
328
 
303
- def get_completion_of_node(self: JacLangServer, node: uni.AstNode) -> lspt.CompletionList {
329
+ def get_completion_of_node(
330
+ self: JacLangServer, node: uni.AstNode
331
+ ) -> lspt.CompletionList {
304
332
  if (node_type := self.get_node_type(node)) {
305
333
  self.debug("found type " + str(node_type));
306
334
  return self.get_completion_items_of(node_type);
@@ -308,7 +336,6 @@ class JacLangServer(JacProgram , LanguageServer) {
308
336
  self.debug("found scope node " + str(node));
309
337
  return self.get_completion_items_of(node);
310
338
  }
311
-
312
339
  self.debug("no type found for node " + str(node));
313
340
  return lspt.CompletionList(is_incomplete=False, items=[]);
314
341
  }
@@ -325,12 +352,13 @@ class JacLangServer(JacProgram , LanguageServer) {
325
352
  }
326
353
 
327
354
  """Get type members for completion."""
328
- def get_completion_items_of(self: JacLangServer, ty: TypeBase | uni.UniScopeNode) -> lspt.CompletionList {
355
+ def get_completion_items_of(
356
+ self: JacLangServer, ty: TypeBase | uni.UniScopeNode
357
+ ) -> lspt.CompletionList {
329
358
  evaluator = self.get_type_evaluator();
330
359
  self.debug("getting completion of " + str(ty));
331
360
  items = get_completion_items(ty);
332
361
  self.debug("completion items are " + str(items));
333
-
334
362
  items: list[lspt.CompletionItem] = [];
335
363
  for item in get_completion_items(ty) {
336
364
  detail: lspt.CompletionItemLabelDetails | None = None;
@@ -339,22 +367,16 @@ class JacLangServer(JacProgram , LanguageServer) {
339
367
  }
340
368
  items.append(
341
369
  lspt.CompletionItem(
342
- label=item.label,
343
- kind=item.kind,
344
- label_details=detail,
370
+ label=item.label, kind=item.kind, label_details=detail,
345
371
  )
346
372
  );
347
373
  }
348
-
349
- return lspt.CompletionList(
350
- is_incomplete=False,
351
- items=items,
352
- );
374
+ return lspt.CompletionList(is_incomplete=False, items=items,);
353
375
  }
354
376
 
355
377
  """Rename module."""
356
378
  def rename_module(self: JacLangServer, old_path: str, new_path: str) -> None {
357
- if old_path in self.mod.hub and new_path != old_path {
379
+ if old_path in self.mod.hub and new_path != old_path {
358
380
  self.mod.hub[new_path] = self.mod.hub[old_path];
359
381
  self.sem_managers[new_path] = self.sem_managers[old_path];
360
382
  del (self.mod.hub[old_path], ) ;
@@ -376,41 +398,36 @@ class JacLangServer(JacProgram , LanguageServer) {
376
398
  def formatted_jac(self: JacLangServer, file_path: str) -> list[lspt.TextEdit] {
377
399
  try {
378
400
  document = self.workspace.get_text_document(file_path);
379
- formatted_text =
380
- JacProgram.jac_str_formatter(
381
- source_str=document.source,
382
- file_path=document.path
383
- );
401
+ formatted_text = JacProgram.jac_str_formatter(
402
+ source_str=document.source, file_path=document.path
403
+ );
384
404
  } except Exception as e {
385
405
  self.log_error(f"'Error during formatting: '{e}");
386
406
  formatted_text = document.source;
387
407
  }
388
- return
389
- [lspt.TextEdit(
390
- range=lspt.Range(
391
- start=lspt.Position(line=0, character=0),
392
- end=lspt.Position(
393
- line=(len(document.source.splitlines()) + 1),
394
- character=0
395
- )
396
- ),
397
- new_text=formatted_text
398
- )];
408
+ return [
409
+ lspt.TextEdit(
410
+ range=lspt.Range(
411
+ start=lspt.Position(line=0, character=0),
412
+ end=lspt.Position(
413
+ line=(len(document.source.splitlines()) + 1), character=0
414
+ )
415
+ ),
416
+ new_text=formatted_text
417
+ )
418
+ ];
399
419
  }
400
420
 
401
421
  """Return hover information for a file."""
402
422
  def get_hover_info(
403
- self: JacLangServer,
404
- file_path: str,
405
- position: lspt.Position
423
+ self: JacLangServer, file_path: str, position: lspt.Position
406
424
  ) -> Optional[lspt.Hover] {
407
425
  node_selected = self.get_token_at_position(file_path, position);
408
426
  value = self.get_node_info(node_selected) if node_selected else None;
409
427
  if value {
410
428
  return lspt.Hover(
411
429
  contents=lspt.MarkupContent(
412
- kind=lspt.MarkupKind.PlainText,
413
- value=f"{value}"
430
+ kind=lspt.MarkupKind.PlainText, value=f"{value}"
414
431
  )
415
432
  );
416
433
  }
@@ -418,24 +435,28 @@ class JacLangServer(JacProgram , LanguageServer) {
418
435
  }
419
436
 
420
437
  """Extract meaningful information from the AST node."""
421
- def get_node_info(self: JacLangServer, sym_node: uni.AstSymbolNode) -> Optional[str] {
438
+ def get_node_info(
439
+ self: JacLangServer, sym_node: uni.AstSymbolNode
440
+ ) -> Optional[str] {
422
441
  try {
423
442
  if isinstance(sym_node, uni.NameAtom) {
424
443
  sym_node = sym_node.name_of;
425
444
  }
426
445
  access = (sym_node.sym.access.value + ' ') if sym_node.sym else None;
427
- node_info =
428
- f"'('{access if access else ''}{sym_node.sym_category.value}') '{sym_node.sym_name}";
446
+ node_info = f"'('{(access if access else '')}{sym_node.sym_category.value}') '{sym_node.sym_name}";
429
447
  if sym_node.name_spec.clean_type {
430
448
  node_info += f"': '{sym_node.name_spec.clean_type}";
431
449
  }
432
- if isinstance(sym_node,uni.AstSymbolNode) and isinstance(sym_node.name_spec.type,ClassType) {
450
+ if (
451
+ isinstance(sym_node, uni.AstSymbolNode)
452
+ and isinstance(sym_node.name_spec.type, ClassType)
453
+ ) {
433
454
  node_info += f"': '{sym_node.name_spec.type.shared.class_name}";
434
455
  }
435
- if isinstance(sym_node, uni.AstDocNode) and sym_node.doc {
456
+ if isinstance(sym_node, uni.AstDocNode) and sym_node.doc {
436
457
  node_info += f"'\n'{sym_node.doc.value}";
437
458
  }
438
- if isinstance(sym_node, uni.Ability) and sym_node.signature {
459
+ if isinstance(sym_node, uni.Ability) and sym_node.signature {
439
460
  node_info += f"'\n'{sym_node.signature.unparse()}";
440
461
  }
441
462
  } except AttributeError as e {
@@ -447,9 +468,7 @@ class JacLangServer(JacProgram , LanguageServer) {
447
468
  """Return document symbols for a file."""
448
469
  def get_outline(self: JacLangServer, file_path: str) -> list[lspt.DocumentSymbol] {
449
470
  fs_path = uris.to_fs_path(file_path);
450
- if fs_path in self.mod.hub
451
- and (root_node := self.mod.hub[fs_path].sym_tab)
452
- {
471
+ if fs_path in self.mod.hub and (root_node := self.mod.hub[fs_path].sym_tab) {
453
472
  return utils.get_symbols_for_outline(root_node);
454
473
  }
455
474
  return [];
@@ -457,116 +476,70 @@ class JacLangServer(JacProgram , LanguageServer) {
457
476
 
458
477
  """Return definition location for a file."""
459
478
  def get_definition(
460
- self: JacLangServer,
461
- file_path: str,
462
- position: lspt.Position
479
+ self: JacLangServer, file_path: str, position: lspt.Position
463
480
  ) -> Optional[lspt.Location] {
481
+ def make_location(node_: uni.UniNode, is_module: bool = False) -> lspt.Location {
482
+ mod_path = node_.type.file_uri if is_module else node_.loc.mod_path;
483
+ decl_range = (
484
+ utils.create_range(node_.loc)
485
+ if not is_module
486
+ else lspt.Range(
487
+ start=lspt.Position(line=0, character=0),
488
+ end=lspt.Position(line=0, character=0)
489
+ )
490
+ );
491
+ return lspt.Location(
492
+ uri=uris.from_fs_path(str(mod_path)), range=decl_range
493
+ );
494
+ }
464
495
  node_selected = self.get_token_at_position(file_path, position);
465
- if node_selected {
466
- if (node_selected.sym.sym_type == SymbolType.MODULE) {
467
- spec = node_selected.sym.decl.parent.resolve_relative_path();
468
- if spec {
469
- spec = spec[ 5 : ] if spec.startswith('File:') else spec;
470
- return lspt.Location(
471
- uri=uris.from_fs_path(spec),
472
- range=lspt.Range(
473
- start=lspt.Position(line=0, character=0),
474
- end=lspt.Position(line=0, character=0)
475
- )
476
- );
477
- } else {
478
- return None;
479
- }
480
- }
481
- if isinstance(node_selected.sym, uni.NameAtom) {
482
- node_selected = node_selected.name_of;
483
- }
484
- elif isinstance(node_selected, uni.Name)
485
- and node_selected.parent
486
- and isinstance(node_selected.parent, uni.ModulePath)
487
- {
488
- spec = node_selected.parent.parent.abs_path;
489
- if spec {
490
- spec = spec[ 5 : ] if spec.startswith('File:') else spec;
491
- return lspt.Location(
492
- uri=uris.from_fs_path(spec),
493
- range=lspt.Range(
494
- start=lspt.Position(line=0, character=0),
495
- end=lspt.Position(line=0, character=0)
496
- )
497
- );
498
- } else {
499
- return None;
500
- }
501
- } elif node_selected.parent
502
- and isinstance(node_selected.parent, uni.ModuleItem)
503
- {
504
- path =
505
- node_selected.parent.abs_path
506
- or node_selected.parent.from_mod_path.abs_path
507
- ;
508
- loc_range = (0, 0, 0, 0);
509
- if path and loc_range {
510
- path = path[ 5 : ] if path.startswith('File:') else path;
511
- return lspt.Location(
512
- uri=uris.from_fs_path(path),
513
- range=lspt.Range(
514
- start=lspt.Position(
515
- line=loc_range[0],
516
- character=loc_range[1]
517
- ),
518
- end=lspt.Position(line=loc_range[2], character=loc_range[3])
519
- )
520
- );
521
- }
522
- } elif isinstance(node_selected, uni.ElementStmt) {
523
- return None;
524
- }
525
- decl_node =
526
- node_selected.parent.body.target
527
- if node_selected.parent
528
- and isinstance(node_selected.parent, uni.AstImplNeedingNode)
529
- and isinstance(node_selected.parent.body, uni.ImplDef)
530
-
531
- else node_selected.sym.decl
532
- if node_selected.sym and node_selected.sym.decl
533
- else node_selected;
534
- if isinstance(decl_node, list) {
535
- valid_path = decl_node[0].loc.mod_path;
536
- } else {
537
- valid_path = decl_node.loc.mod_path;
538
- }
539
- decl_uri = uris.from_fs_path(valid_path);
540
- if isinstance(decl_node, list) {
541
- valid_range = decl_node[0].loc;
542
- } else {
543
- valid_range = decl_node.loc;
544
- }
545
- try {
546
- decl_range = utils.create_range(valid_range);
547
- } except ValueError {
548
- return None;
496
+ if not node_selected {
497
+ return None;
498
+ }
499
+ node_type = node_selected.type;
500
+ ####################################################################
501
+ ## Handle go to def for types and variables ##
502
+ ####################################################################
503
+ if node_type {
504
+ if isinstance(node_type, ModuleType) {
505
+ return make_location(node_selected, is_module=True);
506
+ } elif isinstance(node_type, ClassType) {
507
+ return make_location(node_selected.sym.decl);
508
+ } elif isinstance(node_type, FunctionType) {
509
+ return make_location(node_selected.sym.decl);
549
510
  }
550
- decl_location = lspt.Location(uri=decl_uri, range=decl_range);
551
- return decl_location;
552
- } else {
511
+ }
512
+ ####################################################################
513
+ ## Ad-hoc handling for impl-def go to def ##
514
+ ####################################################################
515
+ parent = node_selected.parent;
516
+ if not parent {
553
517
  return None;
554
518
  }
519
+ if isinstance(parent, uni.ImplDef) and parent.decl_link {
520
+ return make_location(parent.decl_link.sym.decl);
521
+ }
522
+ if isinstance(parent, uni.AstImplNeedingNode) and parent.body {
523
+ return make_location(parent.body.sym.decl);
524
+ }
525
+ if not node_selected or not node_selected.type {
526
+ return None;
527
+ }
528
+ return None;
555
529
  }
556
530
 
557
531
  """Return references for a file."""
558
532
  def get_references(
559
- self: JacLangServer,
560
- file_path: str,
561
- position: lspt.Position
533
+ self: JacLangServer, file_path: str, position: lspt.Position
562
534
  ) -> list[lspt.Location] {
563
535
  node_selected = self.get_token_at_position(file_path, position);
564
- if node_selected and node_selected.sym {
565
- list_of_references: list[lspt.Location] =
566
- [ lspt.Location(
536
+ if node_selected and node_selected.sym {
537
+ list_of_references: list[lspt.Location] = [
538
+ lspt.Location(
567
539
  uri=uris.from_fs_path(cur_node.loc.mod_path),
568
540
  range=utils.create_range(cur_node.loc)
569
- ) for cur_node in node_selected.sym.uses ];
541
+ ) for cur_node in node_selected.sym.uses
542
+ ];
570
543
  return list_of_references;
571
544
  }
572
545
  return [];
@@ -574,21 +547,16 @@ class JacLangServer(JacProgram , LanguageServer) {
574
547
 
575
548
  """Rename a symbol in a file."""
576
549
  def rename_symbol(
577
- self: JacLangServer,
578
- file_path: str,
579
- position: lspt.Position,
580
- new_name: str
550
+ self: JacLangServer, file_path: str, position: lspt.Position, new_name: str
581
551
  ) -> Optional[lspt.WorkspaceEdit] {
582
552
  node_selected = self.get_token_at_position(file_path, position);
583
- if node_selected and node_selected.sym {
553
+ if node_selected and node_selected.sym {
584
554
  changes: dict[(str, list[lspt.TextEdit])] = {};
585
555
  for node in [*node_selected.sym.uses, node_selected.sym.defn[0]] {
586
556
  key = uris.from_fs_path(node.loc.mod_path);
587
- new_edit =
588
- lspt.TextEdit(
589
- range=utils.create_range(node.loc),
590
- new_text=new_name
591
- );
557
+ new_edit = lspt.TextEdit(
558
+ range=utils.create_range(node.loc), new_text=new_name
559
+ );
592
560
  utils.add_unique_text_edit(changes, key, new_edit);
593
561
  }
594
562
  return lspt.WorkspaceEdit(changes=changes);
@@ -629,36 +597,4 @@ class JacLangServer(JacProgram , LanguageServer) {
629
597
  logging.info(message);
630
598
  }
631
599
 
632
- """Run a function with debouncing per file."""
633
- async def _debounced_run(
634
- self: JacLangServer,
635
- func: Callable,
636
- file_uri: str,
637
- delay: float,
638
- task_dict: dict[(str, asyncio.Task)]
639
- ) -> None {
640
- if ((file_uri in task_dict) and not task_dict[file_uri].done() ) {
641
- task_dict[file_uri].cancel();
642
- self.log_py(
643
- f" {func.__name__} ' was cancelled due to debounce for ' {file_uri} "
644
- );
645
- }
646
- async def wrapper() {
647
- try {
648
- await asyncio.sleep(delay);
649
- loop = asyncio.get_event_loop();
650
- result = await loop.run_in_executor(None, func, file_uri);
651
- return result;
652
- } except asyncio.CancelledError {
653
- self.log_py(
654
- f" {func.__name__} ' was cancelled due to debounce for ' {file_uri} "
655
- );
656
- raise ;
657
- }
658
- }
659
- new_task = asyncio.create_task(wrapper());
660
- task_dict[file_uri] = new_task;
661
- return await new_task;
662
- }
663
-
664
600
  }