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.
- jaclang/cli/cli.md +3 -3
- jaclang/cli/cli.py +37 -37
- jaclang/cli/cmdreg.py +45 -140
- jaclang/compiler/constant.py +0 -1
- jaclang/compiler/jac.lark +3 -6
- jaclang/compiler/larkparse/jac_parser.py +2 -2
- jaclang/compiler/parser.py +213 -34
- jaclang/compiler/passes/main/__init__.py +2 -4
- jaclang/compiler/passes/main/def_use_pass.py +0 -4
- jaclang/compiler/passes/main/predynamo_pass.py +221 -0
- jaclang/compiler/passes/main/pyast_gen_pass.py +83 -55
- jaclang/compiler/passes/main/pyast_load_pass.py +66 -40
- jaclang/compiler/passes/main/sym_tab_build_pass.py +1 -1
- jaclang/compiler/passes/main/tests/fixtures/checker/import_sym.jac +2 -0
- jaclang/compiler/passes/main/tests/fixtures/checker/import_sym_test.jac +6 -0
- jaclang/compiler/passes/main/tests/fixtures/checker/imported_sym.jac +5 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_arg_param_match.jac +37 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_arity.jac +18 -0
- 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_cat_is_animal.jac +18 -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_float.jac +7 -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/fixtures/checker_param_types.jac +11 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_self_type.jac +9 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_sym_inherit.jac +42 -0
- jaclang/compiler/passes/main/tests/fixtures/predynamo_fix3.jac +43 -0
- jaclang/compiler/passes/main/tests/fixtures/predynamo_where_assign.jac +13 -0
- jaclang/compiler/passes/main/tests/fixtures/predynamo_where_return.jac +11 -0
- jaclang/compiler/passes/main/tests/test_checker_pass.py +265 -0
- jaclang/compiler/passes/main/tests/test_predynamo_pass.py +57 -0
- jaclang/compiler/passes/main/type_checker_pass.py +36 -61
- jaclang/compiler/passes/tool/doc_ir_gen_pass.py +204 -44
- jaclang/compiler/passes/tool/jac_formatter_pass.py +119 -69
- jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +3 -3
- jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +4 -5
- jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +171 -11
- jaclang/compiler/passes/transform.py +12 -8
- jaclang/compiler/program.py +14 -6
- jaclang/compiler/tests/fixtures/jac_import_py_files.py +4 -0
- jaclang/compiler/tests/fixtures/jac_module.jac +3 -0
- jaclang/compiler/tests/fixtures/multiple_syntax_errors.jac +10 -0
- jaclang/compiler/tests/fixtures/python_module.py +1 -0
- jaclang/compiler/tests/test_importer.py +39 -0
- jaclang/compiler/tests/test_parser.py +49 -0
- jaclang/compiler/type_system/operations.py +104 -0
- jaclang/compiler/type_system/type_evaluator.py +470 -47
- jaclang/compiler/type_system/type_utils.py +246 -0
- jaclang/compiler/type_system/types.py +58 -2
- jaclang/compiler/unitree.py +79 -94
- jaclang/langserve/engine.jac +253 -230
- jaclang/langserve/server.jac +46 -15
- jaclang/langserve/tests/fixtures/circle.jac +3 -3
- jaclang/langserve/tests/fixtures/circle_err.jac +3 -3
- jaclang/langserve/tests/fixtures/circle_pure.test.jac +3 -3
- jaclang/langserve/tests/fixtures/completion_test_err.jac +10 -0
- jaclang/langserve/tests/server_test/circle_template.jac +80 -0
- jaclang/langserve/tests/server_test/glob_template.jac +4 -0
- jaclang/langserve/tests/server_test/test_lang_serve.py +154 -312
- jaclang/langserve/tests/server_test/utils.py +153 -116
- jaclang/langserve/tests/test_dev_server.py +1 -1
- jaclang/langserve/tests/test_server.py +30 -86
- jaclang/langserve/utils.jac +56 -63
- jaclang/runtimelib/machine.py +7 -0
- jaclang/runtimelib/meta_importer.py +27 -1
- jaclang/runtimelib/tests/fixtures/custom_access_validation.jac +1 -1
- jaclang/runtimelib/tests/fixtures/savable_object.jac +2 -2
- jaclang/settings.py +18 -14
- jaclang/tests/fixtures/abc_check.jac +3 -3
- jaclang/tests/fixtures/arch_rel_import_creation.jac +12 -12
- jaclang/tests/fixtures/chandra_bugs2.jac +3 -3
- jaclang/tests/fixtures/create_dynamic_archetype.jac +13 -13
- 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/fixtures/maxfail_run_test.jac +4 -4
- jaclang/tests/fixtures/params/param_syntax_err.jac +9 -0
- jaclang/tests/fixtures/params/test_complex_params.jac +42 -0
- jaclang/tests/fixtures/params/test_failing_kwonly.jac +207 -0
- jaclang/tests/fixtures/params/test_failing_posonly.jac +116 -0
- jaclang/tests/fixtures/params/test_failing_varargs.jac +300 -0
- jaclang/tests/fixtures/params/test_kwonly_params.jac +29 -0
- jaclang/tests/fixtures/py2jac_params.py +8 -0
- jaclang/tests/fixtures/run_test.jac +4 -4
- jaclang/tests/test_cli.py +103 -18
- jaclang/tests/test_language.py +74 -16
- jaclang/utils/helpers.py +47 -2
- jaclang/utils/module_resolver.py +11 -1
- jaclang/utils/test.py +8 -0
- jaclang/utils/treeprinter.py +0 -18
- {jaclang-0.8.6.dist-info → jaclang-0.8.8.dist-info}/METADATA +3 -3
- {jaclang-0.8.6.dist-info → jaclang-0.8.8.dist-info}/RECORD +99 -62
- {jaclang-0.8.6.dist-info → jaclang-0.8.8.dist-info}/WHEEL +1 -1
- jaclang/compiler/passes/main/inheritance_pass.py +0 -131
- jaclang/langserve/dev_engine.jac +0 -645
- jaclang/langserve/dev_server.jac +0 -201
- jaclang/langserve/tests/server_test/code_test.py +0 -0
- {jaclang-0.8.6.dist-info → jaclang-0.8.8.dist-info}/entry_points.txt +0 -0
|
@@ -2,21 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import tempfile
|
|
5
|
-
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
from lsprotocol.types import (
|
|
9
|
+
DidOpenTextDocumentParams,
|
|
10
|
+
TextDocumentItem,
|
|
11
|
+
DidSaveTextDocumentParams,
|
|
12
|
+
DidChangeTextDocumentParams,
|
|
13
|
+
VersionedTextDocumentIdentifier,
|
|
14
|
+
)
|
|
6
15
|
from jaclang.vendor.pygls.uris import from_fs_path
|
|
7
16
|
from jaclang.vendor.pygls.workspace import Workspace
|
|
8
17
|
|
|
9
|
-
from textwrap import dedent
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def get_jac_file_path():
|
|
13
|
-
"""Return the absolute path to the sample Jac file used for testing."""
|
|
14
|
-
return os.path.abspath(
|
|
15
|
-
os.path.join(
|
|
16
|
-
os.path.dirname(__file__), "../../../../examples/manual_code/circle.jac"
|
|
17
|
-
)
|
|
18
|
-
)
|
|
19
|
-
|
|
20
18
|
|
|
21
19
|
def create_temp_jac_file(initial_content: str = "") -> str:
|
|
22
20
|
"""Create a temporary Jac file with optional initial content and return its path."""
|
|
@@ -28,110 +26,11 @@ def create_temp_jac_file(initial_content: str = "") -> str:
|
|
|
28
26
|
return temp.name
|
|
29
27
|
|
|
30
28
|
|
|
31
|
-
def
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
f
|
|
35
|
-
""
|
|
36
|
-
This module demonstrates a simple circle class and a function to calculate
|
|
37
|
-
the area of a circle in all of Jac's glory.
|
|
38
|
-
"""
|
|
39
|
-
|
|
40
|
-
import math;
|
|
41
|
-
|
|
42
|
-
# Module-level global variable
|
|
43
|
-
glob RAD = 5;
|
|
44
|
-
|
|
45
|
-
"""Function to calculate the area of a circle."""
|
|
46
|
-
def calculate_area(radius: float) -> float {{
|
|
47
|
-
return math.pi * radius * radius;
|
|
48
|
-
}}
|
|
49
|
-
|
|
50
|
-
#* (This is a multiline comment in Jac)
|
|
51
|
-
Above we have the demonstration of a function to calculate the area of a circle.
|
|
52
|
-
Below we have the demonstration of a class to calculate the area of a circle.
|
|
53
|
-
*#
|
|
54
|
-
|
|
55
|
-
"""Enum for shape types"""
|
|
56
|
-
enum ShapeType {{
|
|
57
|
-
CIRCLE = "Circle",
|
|
58
|
-
UNKNOWN = "Unknown"
|
|
59
|
-
}}
|
|
60
|
-
|
|
61
|
-
"""Base class for a shape."""
|
|
62
|
-
obj Shape {{
|
|
63
|
-
has shape_type: ShapeType;
|
|
64
|
-
|
|
65
|
-
"""Abstract method to calculate the area of a shape."""
|
|
66
|
-
def area -> float abs;
|
|
67
|
-
}}
|
|
68
|
-
|
|
69
|
-
"""Circle class inherits from Shape."""
|
|
70
|
-
obj Circle(Shape) {{
|
|
71
|
-
def init(radius: float) {{
|
|
72
|
-
super.init(ShapeType.CIRCLE);
|
|
73
|
-
self.radius = radius;
|
|
74
|
-
}}
|
|
75
|
-
|
|
76
|
-
"""Overridden method to calculate the area of the circle."""
|
|
77
|
-
override def area -> float {{
|
|
78
|
-
return math.pi * self.radius * self.radius;
|
|
79
|
-
}}
|
|
80
|
-
}}
|
|
81
|
-
|
|
82
|
-
with entry {{
|
|
83
|
-
c = Circle(RAD);
|
|
84
|
-
}}
|
|
85
|
-
|
|
86
|
-
# Global also works here
|
|
87
|
-
|
|
88
|
-
with entry:__main__ {{
|
|
89
|
-
# To run the program functionality
|
|
90
|
-
print(
|
|
91
|
-
f"Area of a circle with radius {{RAD}} using function: {{calculate_area(RAD)}}"
|
|
92
|
-
);
|
|
93
|
-
print(
|
|
94
|
-
f"Area of a {{c.shape_type.value}} with radius {{RAD}} using class: {{c.area()}}"
|
|
95
|
-
);
|
|
96
|
-
}}
|
|
97
|
-
|
|
98
|
-
# Unit Tests!
|
|
99
|
-
glob expected_area = 78.53981633974483;
|
|
100
|
-
{code}
|
|
101
|
-
|
|
102
|
-
test calc_area {{
|
|
103
|
-
check almostEqual(calculate_area(RAD), expected_area);
|
|
104
|
-
}}
|
|
105
|
-
|
|
106
|
-
test circle_area {{
|
|
107
|
-
c = Circle(RAD);
|
|
108
|
-
check almostEqual(c.area(), expected_area);
|
|
109
|
-
}}
|
|
110
|
-
|
|
111
|
-
test circle_type {{
|
|
112
|
-
c = Circle(RAD);
|
|
113
|
-
check c.shape_type == ShapeType.CIRCLE;
|
|
114
|
-
}}
|
|
115
|
-
'''
|
|
116
|
-
)
|
|
117
|
-
return jac_code
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def get_simple_code(code: str) -> str:
|
|
121
|
-
"""Generate a sample Jac code snippet with optional test code injected."""
|
|
122
|
-
jac_code = dedent(
|
|
123
|
-
f"""
|
|
124
|
-
|
|
125
|
-
# Unit Tests!
|
|
126
|
-
glob expected_area0 = 78.53981633974483;
|
|
127
|
-
glob expected_area1 = 78.53981633974483;
|
|
128
|
-
{code}
|
|
129
|
-
glob expected_area2 = 78.53981633974483;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"""
|
|
133
|
-
)
|
|
134
|
-
return jac_code
|
|
29
|
+
def load_jac_template(template_file: str, code: str = "") -> str:
|
|
30
|
+
"""Load a Jac template file and inject code into placeholder."""
|
|
31
|
+
with open(template_file, "r") as f:
|
|
32
|
+
jac_template = f.read()
|
|
33
|
+
return jac_template.replace("#{{INJECT_CODE}}", code)
|
|
135
34
|
|
|
136
35
|
|
|
137
36
|
def create_ls_with_workspace(file_path: str):
|
|
@@ -142,3 +41,141 @@ def create_ls_with_workspace(file_path: str):
|
|
|
142
41
|
uri = from_fs_path(file_path)
|
|
143
42
|
ls.lsp._workspace = Workspace(os.path.dirname(file_path), ls)
|
|
144
43
|
return uri, ls
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class TestFile:
|
|
48
|
+
"""Encapsulates test file information and operations."""
|
|
49
|
+
|
|
50
|
+
path: str
|
|
51
|
+
uri: str
|
|
52
|
+
code: str
|
|
53
|
+
version: int = 1
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_template(cls, template_name: str, content: str = "") -> "TestFile":
|
|
57
|
+
"""Create a test file from a template."""
|
|
58
|
+
code = load_jac_template(cls._get_template_path(template_name), content)
|
|
59
|
+
temp_path = create_temp_jac_file(code)
|
|
60
|
+
uri = from_fs_path(temp_path)
|
|
61
|
+
return cls(
|
|
62
|
+
path=temp_path,
|
|
63
|
+
uri=uri or "",
|
|
64
|
+
code=code,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def _get_template_path(file_name: str) -> str:
|
|
69
|
+
"""Get absolute path to test template file."""
|
|
70
|
+
return os.path.abspath(
|
|
71
|
+
os.path.join(os.path.dirname(__file__), file_name)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def cleanup(self):
|
|
75
|
+
"""Remove temporary test file."""
|
|
76
|
+
if os.path.exists(self.path):
|
|
77
|
+
os.remove(self.path)
|
|
78
|
+
|
|
79
|
+
def increment_version(self) -> int:
|
|
80
|
+
"""Increment and return the version number."""
|
|
81
|
+
self.version += 1
|
|
82
|
+
return self.version
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class LanguageServerTestHelper:
|
|
86
|
+
"""Helper class for language server testing operations."""
|
|
87
|
+
|
|
88
|
+
def __init__(self, ls, test_file: TestFile):
|
|
89
|
+
self.ls = ls
|
|
90
|
+
self.test_file = test_file
|
|
91
|
+
|
|
92
|
+
async def open_document(self) -> None:
|
|
93
|
+
"""Open a document in the language server."""
|
|
94
|
+
from jaclang.langserve.server import did_open
|
|
95
|
+
|
|
96
|
+
params = DidOpenTextDocumentParams(
|
|
97
|
+
text_document=TextDocumentItem(
|
|
98
|
+
uri=self.test_file.uri,
|
|
99
|
+
language_id="jac",
|
|
100
|
+
version=self.test_file.version,
|
|
101
|
+
text=self.test_file.code,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
await did_open(self.ls, params)
|
|
105
|
+
|
|
106
|
+
async def save_document(self, code: Optional[str] = None) -> None:
|
|
107
|
+
"""Save a document in the language server."""
|
|
108
|
+
from jaclang.langserve.server import did_save
|
|
109
|
+
|
|
110
|
+
content = code if code is not None else self.test_file.code
|
|
111
|
+
version = self.test_file.increment_version()
|
|
112
|
+
|
|
113
|
+
if code:
|
|
114
|
+
self._update_workspace(code, version)
|
|
115
|
+
|
|
116
|
+
from lsprotocol.types import TextDocumentIdentifier
|
|
117
|
+
|
|
118
|
+
params = DidSaveTextDocumentParams(
|
|
119
|
+
text_document=TextDocumentIdentifier(uri=self.test_file.uri),
|
|
120
|
+
text=content
|
|
121
|
+
)
|
|
122
|
+
await did_save(self.ls, params)
|
|
123
|
+
|
|
124
|
+
async def change_document(self, code: str) -> None:
|
|
125
|
+
"""Change document content in the language server."""
|
|
126
|
+
from jaclang.langserve.server import did_change
|
|
127
|
+
|
|
128
|
+
version = self.test_file.increment_version()
|
|
129
|
+
self._update_workspace(code, version)
|
|
130
|
+
|
|
131
|
+
params = DidChangeTextDocumentParams(
|
|
132
|
+
text_document=VersionedTextDocumentIdentifier(
|
|
133
|
+
uri=self.test_file.uri,
|
|
134
|
+
version=version
|
|
135
|
+
),
|
|
136
|
+
content_changes=[{"text": code}], # type: ignore
|
|
137
|
+
)
|
|
138
|
+
await did_change(self.ls, params)
|
|
139
|
+
|
|
140
|
+
def _update_workspace(self, code: str, version: int) -> None:
|
|
141
|
+
"""Update workspace with new document content."""
|
|
142
|
+
self.ls.workspace.put_text_document(
|
|
143
|
+
TextDocumentItem(
|
|
144
|
+
uri=self.test_file.uri,
|
|
145
|
+
language_id="jac",
|
|
146
|
+
version=version,
|
|
147
|
+
text=code,
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def get_diagnostics(self) -> list:
|
|
152
|
+
"""Get diagnostics for the current document."""
|
|
153
|
+
return self.ls.diagnostics.get(self.test_file.uri, [])
|
|
154
|
+
|
|
155
|
+
def get_semantic_tokens(self):
|
|
156
|
+
"""Get semantic tokens for the current document."""
|
|
157
|
+
return self.ls.get_semantic_tokens(self.test_file.uri)
|
|
158
|
+
|
|
159
|
+
def assert_no_diagnostics(self) -> None:
|
|
160
|
+
"""Assert that there are no diagnostics."""
|
|
161
|
+
diagnostics = self.get_diagnostics()
|
|
162
|
+
assert isinstance(diagnostics, list)
|
|
163
|
+
assert len(diagnostics) == 0, f"Expected no diagnostics, found {len(diagnostics)}"
|
|
164
|
+
|
|
165
|
+
def assert_has_diagnostics(self, count: int = 1, message_contains: Optional[str] = None) -> None:
|
|
166
|
+
"""Assert that diagnostics exist with optional message validation."""
|
|
167
|
+
diagnostics = self.get_diagnostics()
|
|
168
|
+
assert isinstance(diagnostics, list)
|
|
169
|
+
assert len(diagnostics) == count, f"Expected {count} diagnostic(s), found {len(diagnostics)}"
|
|
170
|
+
|
|
171
|
+
if message_contains:
|
|
172
|
+
assert message_contains in diagnostics[0].message, \
|
|
173
|
+
f"Expected '{message_contains}' in diagnostic message"
|
|
174
|
+
|
|
175
|
+
def assert_semantic_tokens_count(self, expected_count: int) -> None:
|
|
176
|
+
"""Assert semantic tokens data has expected count."""
|
|
177
|
+
tokens = self.get_semantic_tokens()
|
|
178
|
+
assert hasattr(tokens, "data")
|
|
179
|
+
assert isinstance(tokens.data, list)
|
|
180
|
+
assert len(tokens.data) == expected_count, \
|
|
181
|
+
f"Expected {expected_count} tokens, found {len(tokens.data)}"
|
|
@@ -3,7 +3,7 @@ from jaclang.vendor.pygls import uris
|
|
|
3
3
|
from jaclang.vendor.pygls.workspace import Workspace
|
|
4
4
|
|
|
5
5
|
import lsprotocol.types as lspt
|
|
6
|
-
from jaclang.langserve.
|
|
6
|
+
from jaclang.langserve.engine import JacLangServer
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class TestJacLangServer(TestCase):
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
1
2
|
from jaclang.utils.test import TestCase
|
|
2
3
|
from jaclang.vendor.pygls import uris
|
|
3
4
|
from jaclang.vendor.pygls.workspace import Workspace
|
|
@@ -127,6 +128,9 @@ class TestJacLangServer(TestCase):
|
|
|
127
128
|
str(lsp.get_definition(circle_file, lspt.Position(20, 16))),
|
|
128
129
|
)
|
|
129
130
|
|
|
131
|
+
@pytest.mark.xfail(
|
|
132
|
+
reason="Incorrect handling of 'self' in DefUsePass.TODO: Fix chain_use_lookup."
|
|
133
|
+
)
|
|
130
134
|
def test_go_to_definition_method(self) -> None:
|
|
131
135
|
"""Test that the go to definition is correct."""
|
|
132
136
|
lsp = JacLangServer()
|
|
@@ -307,7 +311,6 @@ class TestJacLangServer(TestCase):
|
|
|
307
311
|
for token_type, expected_count in expected_counts:
|
|
308
312
|
self.assertEqual(str(sem_list).count(token_type), expected_count)
|
|
309
313
|
|
|
310
|
-
@pytest.mark.xfail(reason="TODO: Fix when we have the type checker")
|
|
311
314
|
def test_completion(self) -> None:
|
|
312
315
|
"""Test that the completions are correct."""
|
|
313
316
|
lsp = JacLangServer()
|
|
@@ -315,95 +318,28 @@ class TestJacLangServer(TestCase):
|
|
|
315
318
|
workspace = Workspace(workspace_path, lsp)
|
|
316
319
|
lsp.lsp._workspace = workspace
|
|
317
320
|
base_module_file = uris.from_fs_path(
|
|
318
|
-
self.fixture_abs_path("
|
|
321
|
+
self.fixture_abs_path("completion_test_err.jac")
|
|
319
322
|
)
|
|
320
323
|
lsp.deep_check(base_module_file)
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
(
|
|
330
|
-
lspt.Position(
|
|
331
|
-
["
|
|
332
|
-
3,
|
|
333
|
-
),
|
|
334
|
-
(
|
|
335
|
-
lspt.Position(65, 13),
|
|
336
|
-
[
|
|
337
|
-
"get_color1",
|
|
338
|
-
"color1",
|
|
339
|
-
"point1",
|
|
340
|
-
"base_colorred",
|
|
341
|
-
"pointred",
|
|
342
|
-
"inner_red",
|
|
343
|
-
"doubleinner",
|
|
344
|
-
"apply_red",
|
|
345
|
-
],
|
|
346
|
-
11,
|
|
347
|
-
),
|
|
348
|
-
(
|
|
349
|
-
lspt.Position(65, 23),
|
|
350
|
-
["color22", "doublepoint22", "point22", "apply_inner_red", "enum_red"],
|
|
351
|
-
5,
|
|
352
|
-
),
|
|
353
|
-
(
|
|
354
|
-
lspt.Position(65, 31),
|
|
355
|
-
["RED22", "GREEN22", "BLUE22"],
|
|
356
|
-
3,
|
|
357
|
-
),
|
|
358
|
-
(
|
|
359
|
-
lspt.Position(35, 28),
|
|
360
|
-
[],
|
|
361
|
-
0,
|
|
362
|
-
),
|
|
363
|
-
(
|
|
364
|
-
lspt.Position(72, 12),
|
|
365
|
-
[
|
|
366
|
-
"get_color1",
|
|
367
|
-
"color1",
|
|
368
|
-
"point1",
|
|
369
|
-
"base_colorred",
|
|
370
|
-
"pointred",
|
|
371
|
-
"inner_red",
|
|
372
|
-
"doubleinner",
|
|
373
|
-
"apply_red",
|
|
374
|
-
],
|
|
375
|
-
11,
|
|
376
|
-
),
|
|
377
|
-
(
|
|
378
|
-
lspt.Position(73, 22),
|
|
379
|
-
["color22", "doublepoint22", "apply_inner_red", "point22", "enum_red"],
|
|
380
|
-
5,
|
|
381
|
-
),
|
|
382
|
-
(
|
|
383
|
-
lspt.Position(37, 12),
|
|
384
|
-
["self", "add", "subtract", "x", "Colorenum", "Colour1", "red", "r"],
|
|
385
|
-
153,
|
|
386
|
-
None,
|
|
324
|
+
|
|
325
|
+
@dataclass
|
|
326
|
+
class Case:
|
|
327
|
+
pos: lspt.Position
|
|
328
|
+
expected: list[str]
|
|
329
|
+
trigger: str = "."
|
|
330
|
+
|
|
331
|
+
test_cases: list[Case] = [
|
|
332
|
+
Case(
|
|
333
|
+
lspt.Position(8, 8),
|
|
334
|
+
["bar", "baz"],
|
|
387
335
|
),
|
|
388
336
|
]
|
|
389
|
-
default_trigger = "."
|
|
390
337
|
for case in test_cases:
|
|
391
|
-
position, expected_completions, expected_length = case[:3]
|
|
392
|
-
completion_trigger = case[3] if len(case) > 3 else default_trigger
|
|
393
338
|
completions = lsp.get_completion(
|
|
394
|
-
base_module_file,
|
|
339
|
+
base_module_file, case.pos, completion_trigger=case.trigger
|
|
395
340
|
).items
|
|
396
|
-
for completion in
|
|
341
|
+
for completion in case.expected:
|
|
397
342
|
self.assertIn(completion, str(completions))
|
|
398
|
-
self.assertEqual(expected_length, len(completions))
|
|
399
|
-
|
|
400
|
-
if position == lspt.Position(73, 12):
|
|
401
|
-
self.assertEqual(
|
|
402
|
-
2, str(completions).count("kind=<CompletionItemKind.Function: 3>")
|
|
403
|
-
)
|
|
404
|
-
self.assertEqual(
|
|
405
|
-
4, str(completions).count("kind=<CompletionItemKind.Field: 5>")
|
|
406
|
-
)
|
|
407
343
|
|
|
408
344
|
def test_go_to_reference(self) -> None:
|
|
409
345
|
"""Test that the go to reference is correct."""
|
|
@@ -416,8 +352,8 @@ class TestJacLangServer(TestCase):
|
|
|
416
352
|
lsp.deep_check(circle_file)
|
|
417
353
|
test_cases = [
|
|
418
354
|
(47, 12, ["circle.jac:47:8-47:14", "69:8-69:14", "74:8-74:14"]),
|
|
419
|
-
(54, 66, ["54:62-54:76", "65:
|
|
420
|
-
(62, 14, ["65:
|
|
355
|
+
(54, 66, ["54:62-54:76", "65:23-65:37"]),
|
|
356
|
+
(62, 14, ["65:44-65:57", "70:33-70:46"]),
|
|
421
357
|
]
|
|
422
358
|
for line, char, expected_refs in test_cases:
|
|
423
359
|
references = str(lsp.get_references(circle_file, lspt.Position(line, char)))
|
|
@@ -595,9 +531,17 @@ class TestJacLangServer(TestCase):
|
|
|
595
531
|
workspace_path = self.fixture_abs_path("")
|
|
596
532
|
workspace = Workspace(workspace_path, lsp)
|
|
597
533
|
lsp.lsp._workspace = workspace
|
|
598
|
-
guess_game_file = uris.from_fs_path(
|
|
534
|
+
guess_game_file = uris.from_fs_path(
|
|
535
|
+
self.fixture_abs_path(
|
|
536
|
+
"../../../compiler/passes/main/tests/fixtures/sym_binder.jac"
|
|
537
|
+
)
|
|
538
|
+
)
|
|
599
539
|
lsp.deep_check(guess_game_file)
|
|
600
540
|
self.assertIn(
|
|
601
541
|
"/tests/fixtures/M1.jac:0:0-0:0",
|
|
602
542
|
str(lsp.get_definition(guess_game_file, lspt.Position(29, 9))),
|
|
603
|
-
)
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
TestJacLangServer().test_completion()
|
jaclang/langserve/utils.jac
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Utility functions for the language server."""
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
import asyncio;
|
|
4
5
|
import builtins;
|
|
5
6
|
import re;
|
|
@@ -31,18 +32,18 @@ glob T = TypeVar('T', bound=Callable[(P, Coroutine[(Any, Any, Any)])]);
|
|
|
31
32
|
"""Return diagnostics."""
|
|
32
33
|
def gen_diagnostics(
|
|
33
34
|
from_path: str,
|
|
34
|
-
errors:
|
|
35
|
-
warnings:
|
|
36
|
-
) ->
|
|
35
|
+
errors: list[Alert],
|
|
36
|
+
warnings: list[Alert]
|
|
37
|
+
) -> list[lspt.Diagnostic] {
|
|
37
38
|
return [ lspt.Diagnostic(
|
|
38
39
|
range=create_range(error.loc),
|
|
39
40
|
message=error.msg,
|
|
40
41
|
severity=lspt.DiagnosticSeverity.Error
|
|
41
|
-
) for error in errors if error.loc.mod_path ==
|
|
42
|
+
) for error in errors if error.loc.mod_path == from_path ] + [ lspt.Diagnostic(
|
|
42
43
|
range=create_range(warning.loc),
|
|
43
44
|
message=warning.msg,
|
|
44
45
|
severity=lspt.DiagnosticSeverity.Warning
|
|
45
|
-
) for warning in warnings if warning.loc.mod_path ==
|
|
46
|
+
) for warning in warnings if warning.loc.mod_path == from_path ];
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
|
|
@@ -75,7 +76,7 @@ def debounce(<>wait: float) -> Callable[[T], Callable[(..., Awaitable[None])]] {
|
|
|
75
76
|
|
|
76
77
|
|
|
77
78
|
"""Iterate through symbol table."""
|
|
78
|
-
def sym_tab_list(sym_tab: UniScopeNode, file_path: str) ->
|
|
79
|
+
def sym_tab_list(sym_tab: UniScopeNode, file_path: str) -> list[UniScopeNode] {
|
|
79
80
|
sym_tabs = [sym_tab]
|
|
80
81
|
if not (isinstance(sym_tab, uni.Module) and sym_tab.loc.mod_path != file_path )
|
|
81
82
|
else []
|
|
@@ -112,30 +113,26 @@ def find_deepest_symbol_node_at_pos(
|
|
|
112
113
|
|
|
113
114
|
|
|
114
115
|
"""Check if the position falls within the node's location."""
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
# All lines and columns are 1-Based
|
|
117
|
+
def position_within_node(n: uni.UniNode, line: int, character: int) -> bool {
|
|
118
|
+
if n.loc.first_line < line < n.loc.last_line {
|
|
117
119
|
return True;
|
|
118
120
|
}
|
|
119
|
-
if
|
|
120
|
-
|
|
121
|
-
and <>node.loc.last_line == (line + 1)
|
|
122
|
-
and <>node.loc.col_end >= (character + 1)
|
|
123
|
-
|
|
124
|
-
or <>node.loc.last_line > (line + 1)
|
|
125
|
-
{
|
|
126
|
-
return True;
|
|
121
|
+
if line < n.loc.first_line or line > n.loc.last_line {
|
|
122
|
+
return False;
|
|
127
123
|
}
|
|
128
|
-
if
|
|
129
|
-
|
|
130
|
-
{
|
|
131
|
-
return True;
|
|
124
|
+
if line == n.loc.first_line and character < n.loc.col_start {
|
|
125
|
+
return False;
|
|
132
126
|
}
|
|
133
|
-
|
|
127
|
+
if line == n.loc.last_line and character >= n.loc.col_end {
|
|
128
|
+
return False;
|
|
129
|
+
}
|
|
130
|
+
return True;
|
|
134
131
|
}
|
|
135
132
|
|
|
136
133
|
|
|
137
134
|
"""Find index."""
|
|
138
|
-
def find_index(sem_tokens:
|
|
135
|
+
def find_index(sem_tokens: list[int], line: int, char: int) -> Optional[int] {
|
|
139
136
|
index = None;
|
|
140
137
|
token_start_list = [ get_token_start(i, sem_tokens) for i in range(0, len(sem_tokens), 5) ];
|
|
141
138
|
for (i, j) in enumerate(token_start_list) {
|
|
@@ -148,7 +145,7 @@ def find_index(sem_tokens: <>list[int], line: int, char: int) -> Optional[int] {
|
|
|
148
145
|
|
|
149
146
|
|
|
150
147
|
"""Recursively collect symbols from the AST."""
|
|
151
|
-
def get_symbols_for_outline(<>node: UniScopeNode) ->
|
|
148
|
+
def get_symbols_for_outline(<>node: UniScopeNode) -> list[lspt.DocumentSymbol] {
|
|
152
149
|
symbols = [];
|
|
153
150
|
for (key, item) in <>node.names_in_scope.items() {
|
|
154
151
|
if key in dir(builtins)
|
|
@@ -234,34 +231,30 @@ def kind_map(sub_tab: uni.UniNode) -> lspt.SymbolKind {
|
|
|
234
231
|
|
|
235
232
|
"""Map the symbol node to an lspt.CompletionItemKind."""
|
|
236
233
|
def label_map(sub_tab: SymbolType) -> lspt.CompletionItemKind {
|
|
237
|
-
return
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
;
|
|
234
|
+
return (
|
|
235
|
+
lspt.CompletionItemKind.Function
|
|
236
|
+
if sub_tab in [SymbolType.ABILITY, SymbolType.TEST]
|
|
237
|
+
else lspt.CompletionItemKind.Class
|
|
238
|
+
if sub_tab in [
|
|
239
|
+
SymbolType.OBJECT_ARCH,
|
|
240
|
+
SymbolType.NODE_ARCH,
|
|
241
|
+
SymbolType.EDGE_ARCH,
|
|
242
|
+
SymbolType.WALKER_ARCH
|
|
243
|
+
]
|
|
244
|
+
else lspt.CompletionItemKind.Module
|
|
245
|
+
if sub_tab == SymbolType.MODULE
|
|
246
|
+
else lspt.CompletionItemKind.Enum
|
|
247
|
+
if sub_tab == SymbolType.ENUM_ARCH
|
|
248
|
+
else lspt.CompletionItemKind.Field
|
|
249
|
+
if sub_tab == SymbolType.HAS_VAR
|
|
250
|
+
else lspt.CompletionItemKind.Method
|
|
251
|
+
if sub_tab == SymbolType.METHOD
|
|
252
|
+
else lspt.CompletionItemKind.EnumMember
|
|
253
|
+
if sub_tab == SymbolType.ENUM_MEMBER
|
|
254
|
+
else lspt.CompletionItemKind.Interface
|
|
255
|
+
if sub_tab == SymbolType.IMPL
|
|
256
|
+
else lspt.CompletionItemKind.Variable
|
|
257
|
+
);
|
|
265
258
|
}
|
|
266
259
|
|
|
267
260
|
|
|
@@ -269,9 +262,9 @@ def label_map(sub_tab: SymbolType) -> lspt.CompletionItemKind {
|
|
|
269
262
|
def collect_all_symbols_in_scope(
|
|
270
263
|
sym_tab: UniScopeNode,
|
|
271
264
|
up_tree: bool = True
|
|
272
|
-
) ->
|
|
265
|
+
) -> list[lspt.CompletionItem] {
|
|
273
266
|
symbols = [];
|
|
274
|
-
visited =
|
|
267
|
+
visited = set();
|
|
275
268
|
current_tab : Optional[UniScopeNode] = sym_tab;
|
|
276
269
|
while current_tab is not None and current_tab not in visited {
|
|
277
270
|
visited.add(current_tab);
|
|
@@ -295,8 +288,8 @@ def collect_all_symbols_in_scope(
|
|
|
295
288
|
|
|
296
289
|
|
|
297
290
|
"""Return all child tab's as completion items."""
|
|
298
|
-
def collect_child_tabs(sym_tab: UniScopeNode) ->
|
|
299
|
-
symbols :
|
|
291
|
+
def collect_child_tabs(sym_tab: UniScopeNode) -> list[lspt.CompletionItem] {
|
|
292
|
+
symbols : list[lspt.CompletionItem] = [];
|
|
300
293
|
for tab in sym_tab.kid_scope {
|
|
301
294
|
if tab.scope_name not in [ i.label for i in symbols ] {
|
|
302
295
|
symbols.append(
|
|
@@ -312,7 +305,7 @@ def collect_child_tabs(sym_tab: UniScopeNode) -> <>list[lspt.CompletionItem] {
|
|
|
312
305
|
|
|
313
306
|
|
|
314
307
|
"""Parse text and return a list of symbols."""
|
|
315
|
-
def parse_symbol_path(text: str, dot_position: int) ->
|
|
308
|
+
def parse_symbol_path(text: str, dot_position: int) -> list[str] {
|
|
316
309
|
text = text[ : dot_position ][ : -1 ].strip();
|
|
317
310
|
valid_character_pattern = re.compile('[a-zA-Z0-9_]');
|
|
318
311
|
reversed_text = text[ : : -1 ];
|
|
@@ -342,8 +335,8 @@ def parse_symbol_path(text: str, dot_position: int) -> <>list[str] {
|
|
|
342
335
|
"""Return the starting position of a token."""
|
|
343
336
|
def get_token_start(
|
|
344
337
|
token_index: (int | None),
|
|
345
|
-
sem_tokens:
|
|
346
|
-
) ->
|
|
338
|
+
sem_tokens: list[int]
|
|
339
|
+
) -> tuple[int, int, int] {
|
|
347
340
|
if token_index is None or token_index >= len(sem_tokens) {
|
|
348
341
|
return (0, 0, 0);
|
|
349
342
|
}
|
|
@@ -382,8 +375,8 @@ def find_surrounding_tokens(
|
|
|
382
375
|
change_start_char: int,
|
|
383
376
|
change_end_line: int,
|
|
384
377
|
change_end_char: int,
|
|
385
|
-
sem_tokens:
|
|
386
|
-
) ->
|
|
378
|
+
sem_tokens: list[int]
|
|
379
|
+
) -> tuple[(int | None), (int | None), bool] {
|
|
387
380
|
prev_token_index = None;
|
|
388
381
|
next_token_index = None;
|
|
389
382
|
inside_tok = False;
|
|
@@ -422,8 +415,8 @@ def find_surrounding_tokens(
|
|
|
422
415
|
"""Get the line of code, and the first non-space character index."""
|
|
423
416
|
def get_line_of_code(
|
|
424
417
|
line_number: int,
|
|
425
|
-
lines:
|
|
426
|
-
) -> Optional[
|
|
418
|
+
lines: list[str]
|
|
419
|
+
) -> Optional[tuple[(str, int)]] {
|
|
427
420
|
if 0 <= line_number < len(lines) {
|
|
428
421
|
line = lines[line_number].rstrip('\n');
|
|
429
422
|
first_non_space = (len(line) - len(line.lstrip()));
|
|
@@ -439,7 +432,7 @@ def get_line_of_code(
|
|
|
439
432
|
|
|
440
433
|
"""Add a new text edit to the changes dictionary if it is unique."""
|
|
441
434
|
def add_unique_text_edit(
|
|
442
|
-
changes:
|
|
435
|
+
changes: dict[(str, list[lspt.TextEdit])],
|
|
443
436
|
key: str,
|
|
444
437
|
new_edit: lspt.TextEdit
|
|
445
438
|
) -> None {
|