jaclang 0.7.16__py3-none-any.whl → 0.7.17__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 (77) hide show
  1. jaclang/cli/cli.py +140 -75
  2. jaclang/compiler/absyntree.py +9 -4
  3. jaclang/compiler/constant.py +8 -8
  4. jaclang/compiler/parser.py +10 -2
  5. jaclang/compiler/passes/main/__init__.py +1 -1
  6. jaclang/compiler/passes/main/access_modifier_pass.py +96 -147
  7. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +152 -50
  8. jaclang/compiler/passes/main/import_pass.py +88 -59
  9. jaclang/compiler/passes/main/py_collect_dep_pass.py +70 -0
  10. jaclang/compiler/passes/main/pyast_gen_pass.py +21 -6
  11. jaclang/compiler/passes/main/pyast_load_pass.py +1 -0
  12. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +7 -0
  13. jaclang/compiler/passes/main/schedules.py +9 -2
  14. jaclang/compiler/passes/main/sym_tab_build_pass.py +9 -5
  15. jaclang/compiler/passes/main/tests/fixtures/autoimpl.empty.impl.jac +0 -0
  16. jaclang/compiler/passes/main/tests/fixtures/autoimpl.jac +1 -1
  17. jaclang/compiler/passes/main/tests/fixtures/py_imp_test.jac +29 -0
  18. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/__init__.py +3 -0
  19. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/color.py +3 -0
  20. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/constants.py +5 -0
  21. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/display.py +2 -0
  22. jaclang/compiler/passes/main/tests/test_import_pass.py +72 -13
  23. jaclang/compiler/passes/main/type_check_pass.py +15 -5
  24. jaclang/compiler/passes/tool/jac_formatter_pass.py +11 -3
  25. jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +37 -41
  26. jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +37 -41
  27. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/access_mod_check.jac +27 -0
  28. jaclang/compiler/passes/utils/mypy_ast_build.py +45 -0
  29. jaclang/compiler/symtable.py +16 -11
  30. jaclang/compiler/tests/test_importer.py +17 -9
  31. jaclang/langserve/engine.py +39 -0
  32. jaclang/langserve/server.py +16 -1
  33. jaclang/langserve/tests/fixtures/rename.jac +30 -0
  34. jaclang/langserve/tests/test_server.py +216 -2
  35. jaclang/langserve/utils.py +21 -2
  36. jaclang/plugin/default.py +78 -70
  37. jaclang/plugin/feature.py +7 -17
  38. jaclang/plugin/spec.py +6 -19
  39. jaclang/plugin/tests/fixtures/other_root_access.jac +82 -0
  40. jaclang/plugin/tests/test_jaseci.py +414 -42
  41. jaclang/runtimelib/architype.py +481 -333
  42. jaclang/runtimelib/constructs.py +5 -8
  43. jaclang/runtimelib/context.py +89 -69
  44. jaclang/runtimelib/importer.py +15 -15
  45. jaclang/runtimelib/machine.py +66 -2
  46. jaclang/runtimelib/memory.py +134 -75
  47. jaclang/runtimelib/utils.py +17 -10
  48. jaclang/settings.py +2 -4
  49. jaclang/tests/fixtures/access_checker.jac +12 -17
  50. jaclang/tests/fixtures/access_modifier.jac +88 -33
  51. jaclang/tests/fixtures/baddy.jac +3 -0
  52. jaclang/tests/fixtures/bar.jac +34 -0
  53. jaclang/tests/fixtures/edge_node_walk.jac +1 -1
  54. jaclang/tests/fixtures/edge_ops.jac +1 -1
  55. jaclang/tests/fixtures/edges_walk.jac +1 -1
  56. jaclang/tests/fixtures/foo.jac +43 -0
  57. jaclang/tests/fixtures/game1.jac +1 -1
  58. jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
  59. jaclang/tests/fixtures/import.jac +9 -0
  60. jaclang/tests/fixtures/index_slice.jac +30 -0
  61. jaclang/tests/fixtures/pyfunc_1.py +1 -1
  62. jaclang/tests/fixtures/pyfunc_2.py +2 -2
  63. jaclang/tests/fixtures/pygame_mock/__init__.py +3 -0
  64. jaclang/tests/fixtures/pygame_mock/color.py +3 -0
  65. jaclang/tests/fixtures/pygame_mock/constants.py +5 -0
  66. jaclang/tests/fixtures/pygame_mock/display.py +2 -0
  67. jaclang/tests/fixtures/pygame_mock/inner/__init__.py +0 -0
  68. jaclang/tests/fixtures/pygame_mock/inner/iner_mod.py +2 -0
  69. jaclang/tests/test_cli.py +49 -6
  70. jaclang/tests/test_language.py +113 -78
  71. jaclang/tests/test_reference.py +2 -9
  72. jaclang/utils/treeprinter.py +30 -3
  73. {jaclang-0.7.16.dist-info → jaclang-0.7.17.dist-info}/METADATA +1 -1
  74. {jaclang-0.7.16.dist-info → jaclang-0.7.17.dist-info}/RECORD +77 -56
  75. /jaclang/tests/fixtures/{err.test.jac → baddy.test.jac} +0 -0
  76. {jaclang-0.7.16.dist-info → jaclang-0.7.17.dist-info}/WHEEL +0 -0
  77. {jaclang-0.7.16.dist-info → jaclang-0.7.17.dist-info}/entry_points.txt +0 -0
@@ -137,7 +137,7 @@ class SymbolTable:
137
137
  return k
138
138
  return None
139
139
 
140
- def push_scope(self, name: str, key_node: ast.AstNode) -> SymbolTable:
140
+ def push_kid_scope(self, name: str, key_node: ast.AstNode) -> SymbolTable:
141
141
  """Push a new scope onto the symbol table."""
142
142
  self.kid.append(SymbolTable(name, key_node, self))
143
143
  return self.kid[-1]
@@ -205,16 +205,21 @@ class SymbolTable:
205
205
  for i in node_list:
206
206
  if cur_sym_tab is None:
207
207
  break
208
- cur_sym_tab = (
209
- lookup.decl.sym_tab
210
- if (
211
- lookup := self.use_lookup(
212
- i,
213
- sym_table=cur_sym_tab,
214
- )
215
- )
216
- else None
217
- )
208
+ lookup = self.use_lookup(i, sym_table=cur_sym_tab)
209
+ if lookup:
210
+ cur_sym_tab = lookup.decl.sym_tab
211
+
212
+ # check if the symbol table name is not the same as symbol name
213
+ # then try to find a child scope with the same name
214
+ # This is used to get the scope in case of
215
+ # import:py math;
216
+ # b = math.floor(1.7);
217
+ if cur_sym_tab.name != i.sym_name:
218
+ t = cur_sym_tab.find_scope(i.sym_name)
219
+ if t:
220
+ cur_sym_tab = t
221
+ else:
222
+ cur_sym_tab = None
218
223
 
219
224
  def update_py_ctx_for_def(self, node: ast.AstSymbolNode) -> None:
220
225
  """Update python context for definition."""
@@ -5,29 +5,37 @@ import sys
5
5
 
6
6
  from jaclang import jac_import
7
7
  from jaclang.cli import cli
8
- from jaclang.plugin.feature import JacFeature as Jac
8
+ from jaclang.runtimelib.machine import JacMachine, JacProgram
9
9
  from jaclang.utils.test import TestCase
10
10
 
11
11
 
12
12
  class TestLoader(TestCase):
13
13
  """Test Jac self.prse."""
14
14
 
15
- def setUp(self) -> None:
16
- """Set up test."""
17
- return super().setUp()
18
-
19
15
  def test_import_basic_python(self) -> None:
20
16
  """Test basic self loading."""
21
- Jac.context().init_memory(base_path=self.fixture_abs_path(__file__))
17
+ JacMachine(self.fixture_abs_path(__file__)).attach_program(
18
+ JacProgram(mod_bundle=None, bytecode=None)
19
+ )
22
20
  (h,) = jac_import("fixtures.hello_world", base_path=__file__)
23
21
  self.assertEqual(h.hello(), "Hello World!") # type: ignore
22
+ JacMachine.detach()
24
23
 
25
24
  def test_modules_correct(self) -> None:
26
25
  """Test basic self loading."""
27
- Jac.context().init_memory(base_path=self.fixture_abs_path(__file__))
26
+ JacMachine(self.fixture_abs_path(__file__)).attach_program(
27
+ JacProgram(mod_bundle=None, bytecode=None)
28
+ )
28
29
  jac_import("fixtures.hello_world", base_path=__file__)
29
- self.assertIn("module 'fixtures.hello_world'", str(sys.modules))
30
- self.assertIn("/tests/fixtures/hello_world.jac", str(sys.modules))
30
+ self.assertIn(
31
+ "module 'fixtures.hello_world'",
32
+ str(JacMachine.get().loaded_modules),
33
+ )
34
+ self.assertIn(
35
+ "/tests/fixtures/hello_world.jac",
36
+ str(JacMachine.get().loaded_modules),
37
+ )
38
+ JacMachine.detach()
31
39
 
32
40
  def test_jac_py_import(self) -> None:
33
41
  """Basic test for pass."""
@@ -16,6 +16,7 @@ from jaclang.compiler.passes.main.schedules import py_code_gen_typed
16
16
  from jaclang.compiler.passes.tool import FuseCommentsPass, JacFormatPass
17
17
  from jaclang.langserve.sem_manager import SemTokManager
18
18
  from jaclang.langserve.utils import (
19
+ add_unique_text_edit,
19
20
  collect_all_symbols_in_scope,
20
21
  create_range,
21
22
  find_deepest_symbol_node_at_pos,
@@ -127,6 +128,15 @@ class JacLangServer(LanguageServer):
127
128
  build.warnings_had,
128
129
  ),
129
130
  )
131
+ if annex_view:
132
+ self.publish_diagnostics(
133
+ file_path,
134
+ gen_diagnostics(
135
+ file_path,
136
+ build.errors_had,
137
+ build.warnings_had,
138
+ ),
139
+ )
130
140
  self.log_py(f"PROFILE: Deep check took {time.time() - start_time} seconds.")
131
141
  return len(build.errors_had) == 0
132
142
  except Exception as e:
@@ -404,6 +414,35 @@ class JacLangServer(LanguageServer):
404
414
  return list_of_references
405
415
  return []
406
416
 
417
+ def rename_symbol(
418
+ self, file_path: str, position: lspt.Position, new_name: str
419
+ ) -> Optional[lspt.WorkspaceEdit]:
420
+ """Rename a symbol in a file."""
421
+ if file_path not in self.modules:
422
+ return None
423
+ index1 = find_index(
424
+ self.modules[file_path].sem_manager.sem_tokens,
425
+ position.line,
426
+ position.character,
427
+ )
428
+ if index1 is None:
429
+ return None
430
+ node_selected = self.modules[file_path].sem_manager.static_sem_tokens[index1][3]
431
+ if node_selected and node_selected.sym:
432
+ changes: dict[str, list[lspt.TextEdit]] = {}
433
+ for node in [
434
+ *node_selected.sym.uses,
435
+ node_selected.sym.defn[0],
436
+ ]:
437
+ key = uris.from_fs_path(node.loc.mod_path)
438
+ new_edit = lspt.TextEdit(
439
+ range=create_range(node.loc),
440
+ new_text=new_name,
441
+ )
442
+ add_unique_text_edit(changes, key, new_edit)
443
+ return lspt.WorkspaceEdit(changes=changes)
444
+ return None
445
+
407
446
  def get_semantic_tokens(self, file_path: str) -> lspt.SemanticTokens:
408
447
  """Return semantic tokens for a file."""
409
448
  if file_path not in self.modules:
@@ -17,8 +17,14 @@ server = JacLangServer()
17
17
 
18
18
 
19
19
  @server.feature(lspt.TEXT_DOCUMENT_DID_OPEN)
20
- @server.feature(lspt.TEXT_DOCUMENT_DID_SAVE)
21
20
  async def did_open(ls: JacLangServer, params: lspt.DidOpenTextDocumentParams) -> None:
21
+ """Check syntax on change."""
22
+ ls.deep_check(params.text_document.uri)
23
+ ls.lsp.send_request(lspt.WORKSPACE_SEMANTIC_TOKENS_REFRESH)
24
+
25
+
26
+ @server.feature(lspt.TEXT_DOCUMENT_DID_SAVE)
27
+ async def did_save(ls: JacLangServer, params: lspt.DidOpenTextDocumentParams) -> None:
22
28
  """Check syntax on change."""
23
29
  await ls.launch_deep_check(params.text_document.uri)
24
30
  ls.lsp.send_request(lspt.WORKSPACE_SEMANTIC_TOKENS_REFRESH)
@@ -132,6 +138,15 @@ def references(ls: JacLangServer, params: lspt.ReferenceParams) -> list[lspt.Loc
132
138
  return ls.get_references(params.text_document.uri, params.position)
133
139
 
134
140
 
141
+ @server.feature(lspt.TEXT_DOCUMENT_RENAME)
142
+ def rename(
143
+ ls: JacLangServer, params: lspt.RenameParams
144
+ ) -> Optional[lspt.WorkspaceEdit]:
145
+ """Rename symbol."""
146
+ ls.log_warning("Auto Rename is Experimental, Please use with caution.")
147
+ return ls.rename_symbol(params.text_document.uri, params.position, params.new_name)
148
+
149
+
135
150
  @server.feature(
136
151
  lspt.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
137
152
  lspt.SemanticTokensLegend(
@@ -0,0 +1,30 @@
1
+ can foo;
2
+
3
+
4
+
5
+ :can:foo{
6
+ print("foo");
7
+ }
8
+
9
+
10
+ obj out{
11
+ has cnt :int;
12
+ can bar;
13
+ can baz;
14
+ }
15
+
16
+
17
+ :obj:out:can:bar{
18
+ print("bar");
19
+ }
20
+
21
+ :obj:out:can:baz{
22
+ print("baz");
23
+ }
24
+
25
+ with entry{
26
+ foo();
27
+ new = out();
28
+ new.cnt;
29
+ out(1).bar();
30
+ }
@@ -201,6 +201,62 @@ class TestJacLangServer(TestCase):
201
201
  str(lsp.get_definition(import_file, lspt.Position(line, char))),
202
202
  )
203
203
 
204
+ def test_go_to_definition_foolme(self) -> None:
205
+ """Test that the go to definition is correct."""
206
+ lsp = JacLangServer()
207
+ workspace_path = self.fixture_abs_path("")
208
+ workspace = Workspace(workspace_path, lsp)
209
+ lsp.lsp._workspace = workspace
210
+ import_file = uris.from_fs_path(
211
+ self.fixture_abs_path(
212
+ "../../../../jaclang/compiler/passes/main/tests/fixtures/py_imp_test.jac"
213
+ )
214
+ )
215
+ lsp.deep_check(import_file)
216
+ positions = [
217
+ (6, 39, "/pygame_mock/__init__.py:2:0-2:0"),
218
+ (6, 45, "/pygame_mock/constants.py:3:0-4:1"),
219
+ (7, 31, "/pygame_mock/__init__.py:2:0-2:0"),
220
+ (7, 35, "/pygame_mock/constants.py:3:0-4:1"),
221
+ (20, 51, "/py_imp_test.jac:6:4-6:11"),
222
+ (20, 64, "/pygame_mock/constants.py:4:3-4:15"),
223
+ (21, 48, "/py_imp_test.jac:10:4-10:6"),
224
+ (21, 58, "/py_imp_test.jac:11:8-11:15"),
225
+ (21, 68, "/pygame_mock/constants.py:4:3-4:15"),
226
+ (23, 58, "/pygame_mock/constants.py:4:3-4:15"),
227
+ ]
228
+
229
+ for line, char, expected in positions:
230
+ with self.subTest(line=line, char=char):
231
+ self.assertIn(
232
+ expected,
233
+ str(lsp.get_definition(import_file, lspt.Position(line, char))),
234
+ )
235
+
236
+ def test_go_to_definition_index_expr(self) -> None:
237
+ """Test that the go to definition is correct."""
238
+ lsp = JacLangServer()
239
+ workspace_path = self.fixture_abs_path("")
240
+ workspace = Workspace(workspace_path, lsp)
241
+ lsp.lsp._workspace = workspace
242
+ import_file = uris.from_fs_path(
243
+ self.fixture_abs_path("../../../../jaclang/tests/fixtures/index_slice.jac")
244
+ )
245
+ lsp.deep_check(import_file)
246
+ positions = [
247
+ (23, 20, "index_slice.jac:2:8-2:13"),
248
+ (24, 24, "index_slice.jac:2:8-2:13"),
249
+ (27, 33, "index_slice.jac:2:8-2:13"),
250
+ ]
251
+
252
+ for line, char, expected in positions:
253
+ with self.subTest(line=line, char=char):
254
+ print(str(lsp.get_definition(import_file, lspt.Position(line, char))))
255
+ self.assertIn(
256
+ expected,
257
+ str(lsp.get_definition(import_file, lspt.Position(line, char))),
258
+ )
259
+
204
260
  def test_sem_tokens(self) -> None:
205
261
  """Test that the Semantic Tokens are generated correctly."""
206
262
  lsp = JacLangServer()
@@ -214,7 +270,7 @@ class TestJacLangServer(TestCase):
214
270
  ("<JacSemTokenType.VARIABLE: 8>, <JacSemTokenModifier.READONLY: 4>", 12),
215
271
  (
216
272
  "<JacSemTokenType.PROPERTY: 9>, <JacSemTokenModifier.DEFINITION: 2>,",
217
- 19,
273
+ 21,
218
274
  ),
219
275
  (
220
276
  "<JacSemTokenType.PARAMETER: 7>, <JacSemTokenModifier.DECLARATION: 1>,",
@@ -232,7 +288,6 @@ class TestJacLangServer(TestCase):
232
288
  3,
233
289
  ),
234
290
  ]
235
- print(str(sem_list))
236
291
  for token_type, expected_count in expected_counts:
237
292
  self.assertEqual(str(sem_list).count(token_type), expected_count)
238
293
 
@@ -351,3 +406,162 @@ class TestJacLangServer(TestCase):
351
406
  references = str(lsp.get_references(circle_file, lspt.Position(line, char)))
352
407
  for expected in expected_refs:
353
408
  self.assertIn(expected, references)
409
+
410
+ def test_py_type__definition(self) -> None:
411
+ """Test that the go to definition is correct for pythoon imports."""
412
+ lsp = JacLangServer()
413
+ workspace_path = self.fixture_abs_path("")
414
+ workspace = Workspace(workspace_path, lsp)
415
+ lsp.lsp._workspace = workspace
416
+ import_file = uris.from_fs_path(
417
+ self.fixture_abs_path(
418
+ "../../../../jaclang/compiler/passes/main/tests/fixtures/py_imp_test.jac"
419
+ )
420
+ )
421
+ lsp.deep_check(import_file)
422
+ positions = [
423
+ (19, 29, "pygame_mock/color.py:0:0-2:4"),
424
+ (3, 17, "/pygame_mock/__init__.py:0:0-0:0"),
425
+ (20, 45, "pygame_mock/color.py:0:0-2:4"),
426
+ (19, 77, "mock/constants.py:4:3-4:15"),
427
+ (26, 28, "mock/display.py:0:0-1:7"),
428
+ (24, 22, "/argparse.pyi:124:0-249:13"),
429
+ (19, 74, "pygame_mock/constants.py:4:3-4:15"),
430
+ (27, 17, "/stdlib/os/__init__.pyi:50:0-50:3"),
431
+ ]
432
+
433
+ for line, char, expected in positions:
434
+ with self.subTest(line=line, char=char):
435
+ self.assertIn(
436
+ expected,
437
+ str(lsp.get_definition(import_file, lspt.Position(line, char))),
438
+ )
439
+
440
+ def test_py_type__references(self) -> None:
441
+ """Test that the go to definition is correct for pythoon imports."""
442
+ lsp = JacLangServer()
443
+ workspace_path = self.fixture_abs_path("")
444
+ workspace = Workspace(workspace_path, lsp)
445
+ lsp.lsp._workspace = workspace
446
+
447
+ circle_file = uris.from_fs_path(
448
+ self.fixture_abs_path(
449
+ "../../../../jaclang/compiler/passes/main/tests/fixtures/py_imp_test.jac"
450
+ )
451
+ )
452
+ lsp.deep_check(circle_file)
453
+ test_cases = [
454
+ (
455
+ 2,
456
+ 21,
457
+ [
458
+ ":6:21-6:32",
459
+ ":7:11-7:22",
460
+ ":11:25-11:36",
461
+ ":12:15-12:26",
462
+ ":18:33-18:44",
463
+ "19:46-19:57",
464
+ ":19:8-19:19",
465
+ ":19:46-19:57",
466
+ ":20:8-20:19",
467
+ "21:8-21:19,",
468
+ "23:8-23:19",
469
+ ":26:4-26:15",
470
+ ],
471
+ ),
472
+ (
473
+ 19,
474
+ 63,
475
+ [
476
+ "6:33-6:42",
477
+ "7:23-7:32",
478
+ "18:45-18:54",
479
+ "19:58-19:67",
480
+ "11:37-11:46",
481
+ "12:27-12:36",
482
+ ],
483
+ ),
484
+ (
485
+ 24,
486
+ 53,
487
+ [
488
+ "24:42-24:56",
489
+ "24:16-24:30",
490
+ "argparse.pyi:334:21-334:35",
491
+ "argparse.pyi:163:29-163:43",
492
+ "argparse.pyi:32:52-32:66",
493
+ ],
494
+ ),
495
+ ]
496
+ for line, char, expected_refs in test_cases:
497
+ references = str(lsp.get_references(circle_file, lspt.Position(line, char)))
498
+ for expected in expected_refs:
499
+ self.assertIn(expected, references)
500
+
501
+ def test_rename_symbol(self) -> None:
502
+ """Test that the rename is correct."""
503
+ lsp = JacLangServer()
504
+ workspace_path = self.fixture_abs_path("")
505
+ workspace = Workspace(workspace_path, lsp)
506
+ lsp.lsp._workspace = workspace
507
+
508
+ circle_file = uris.from_fs_path(self.fixture_abs_path("circle.jac"))
509
+ lsp.deep_check(circle_file)
510
+ test_cases = [
511
+ (
512
+ 20,
513
+ 14,
514
+ "ShapeKind",
515
+ "27:20-27:29,",
516
+ "36:19-36:28",
517
+ "75:26-75:35",
518
+ "20:5-20:14",
519
+ ),
520
+ (12, 34, "circleRadius", "12:21-12:27", "12:30-12:36", "11:19-11:25"),
521
+ (62, 14, "target_area", "65:43-65:56", "70:32-70:45", "62:5-62:18"),
522
+ (57, 33, "type_of_shape", "75:12-75:22", "27:8-27:18,", "57:23-57:33"),
523
+ ]
524
+ for tup in test_cases:
525
+ line, char, new_name, *expected_refs = tup
526
+ references = str(
527
+ lsp.rename_symbol(circle_file, lspt.Position(line, char), new_name)
528
+ )
529
+ for expected in expected_refs:
530
+ self.assertIn(expected, references)
531
+
532
+ def test_rename_uses(self) -> None:
533
+ """Test that the rename is correct."""
534
+ lsp = JacLangServer()
535
+ workspace_path = self.fixture_abs_path("")
536
+ workspace = Workspace(workspace_path, lsp)
537
+ lsp.lsp._workspace = workspace
538
+
539
+ circle_file = uris.from_fs_path(self.fixture_abs_path("rename.jac"))
540
+ lsp.deep_check(circle_file)
541
+ # fmt: off
542
+ test_cases = [
543
+ (0, 7, "func", "25:4-25:7", "0:4-0:7", "4:5-4:8",),
544
+ (4, 6, "func", "25:4-25:7", "0:4-0:7", "4:5-4:8",),
545
+ (25, 7, "func", "25:4-25:7", "0:4-0:7", "4:5-4:8",),
546
+ (10, 10, "canBar", "27:8-27:11", "10:8-10:11"),
547
+ (27, 9, "canBar", "27:8-27:11", "10:8-10:11"),
548
+ (9, 6, "canBar", "26:10-26:13", "28:4-28:7", "16:5-16:8", "9:4-9:7"),
549
+ (26, 11, "canBar", "26:10-26:13", "28:4-28:7", "16:5-16:8", "9:4-9:7"),
550
+ (16, 7, "canBar", "26:10-26:13", "28:4-28:7", "16:5-16:8", "9:4-9:7"),
551
+ (28, 6, "canBar", "26:10-26:13", "28:4-28:7", "16:5-16:8", "9:4-9:7"),
552
+ (11, 10, "canBar", "11:8-11:11", "16:13-16:16", "28:11-28:14"),
553
+ (16, 14, "canBar", "11:8-11:11", "16:13-16:16", "28:11-28:14"),
554
+ (28, 13, "canBar", "11:8-11:11", "16:13-16:16", "28:11-28:14"),
555
+ (12, 10, "canBaz", "12:8-12:11", "20:13-20:16"),
556
+ (20, 14, "canBaz", "12:8-12:11", "20:13-20:16"),
557
+ (26, 6, "count", "27:4-27:7", "26:4-26:7"),
558
+ (27, 5, "count", "27:4-27:7", "26:4-26:7"),
559
+ ]
560
+ # fmt: on
561
+ for tup in test_cases:
562
+ line, char, new_name, *expected_refs = tup
563
+ references = str(
564
+ lsp.rename_symbol(circle_file, lspt.Position(line, char), new_name)
565
+ )
566
+ for expected in expected_refs:
567
+ self.assertIn(expected, references)
@@ -273,7 +273,9 @@ def label_map(sub_tab: SymbolType) -> lspt.CompletionItemKind:
273
273
  )
274
274
 
275
275
 
276
- def get_mod_path(mod_path: ast.ModulePath, name_node: ast.Name) -> str | None:
276
+ def get_mod_path(
277
+ mod_path: ast.ModulePath, name_node: ast.Name
278
+ ) -> str | None: # TODO: This should go away
277
279
  """Get path for a module import name."""
278
280
  ret_target = None
279
281
  if mod_path.parent and (
@@ -294,7 +296,7 @@ def get_mod_path(mod_path: ast.ModulePath, name_node: ast.Name) -> str | None:
294
296
  else ""
295
297
  )
296
298
  else:
297
- temporary_path_str = mod_path.path_str
299
+ temporary_path_str = mod_path.dot_path_str
298
300
  sys.path.append(os.path.dirname(mod_path.loc.mod_path))
299
301
  spec = importlib.util.find_spec(temporary_path_str)
300
302
  sys.path.remove(os.path.dirname(mod_path.loc.mod_path))
@@ -625,3 +627,20 @@ def get_line_of_code(line_number: int, lines: list[str]) -> Optional[tuple[str,
625
627
  else first_non_space
626
628
  )
627
629
  return None
630
+
631
+
632
+ def add_unique_text_edit(
633
+ changes: dict[str, list[lspt.TextEdit]], key: str, new_edit: lspt.TextEdit
634
+ ) -> None:
635
+ """Add a new text edit to the changes dictionary if it is unique."""
636
+ if key not in changes:
637
+ changes[key] = [new_edit]
638
+ else:
639
+ for existing_edit in changes[key]:
640
+ if (
641
+ existing_edit.range.start == new_edit.range.start
642
+ and existing_edit.range.end == new_edit.range.end
643
+ and existing_edit.new_text == new_edit.new_text
644
+ ):
645
+ return
646
+ changes[key].append(new_edit)