Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2d7d3ea
feat: add SemanticAnalysisPass and update program flow to include it
kugesan1105 Oct 9, 2025
d7bb324
fix: update pytest command options for test execution
kugesan1105 Oct 9, 2025
3831037
fix: correct context update logic in _change_atom_trailer_ctx method
kugesan1105 Oct 10, 2025
6dae022
Merge branch 'main' into rm_defuse
kugesan1105 Oct 13, 2025
262f12d
Adding attribute symbol in lcass to teh class symbol table, Remove un…
kugesan1105 Oct 13, 2025
161d3e9
Enhance self member assignment handling in attribute access for arche…
kugesan1105 Oct 13, 2025
b815b0d
Enhance symbol table handling for assignments and member access in ty…
kugesan1105 Oct 14, 2025
1eefc55
Add return statement handling in type checker and improve symbol reso…
kugesan1105 Oct 14, 2025
c0061e3
add comments for readbility
kugesan1105 Oct 14, 2025
207acf4
Enhance symbol usage tracking in type evaluator by adding use referen…
kugesan1105 Oct 14, 2025
b0912d2
Merge branch 'main' into rm_defuse
kugesan1105 Oct 14, 2025
afdf61d
Add get_has_vars property to Archetype and implement get_has_var_para…
kugesan1105 Oct 15, 2025
df7281f
Merge branch 'main' into rm_defuse
kugesan1105 Oct 15, 2025
b0c01de
Refactor comments in TypeEvaluator and Archetype for clarity
kugesan1105 Oct 15, 2025
f7477ec
Add handling for formatted value nodes in TypeCheckPass
kugesan1105 Oct 16, 2025
65688bb
Merge branch 'main' into rm_defuse
kugesan1105 Oct 16, 2025
6d8e21b
support enum memeber access also
kugesan1105 Oct 16, 2025
10b9f44
Add handling for return statement and formatted value nodes in TypeCh…
kugesan1105 Oct 16, 2025
db3a2a9
Merge branch 'main' into rm_defuse
kugesan1105 Oct 17, 2025
ce3c065
Update tests for Jac language server diagnostics and adjust expected …
kugesan1105 Oct 17, 2025
a03f5a1
Remove unused method `get_has_var_parameters` from `TypeEvaluator` class
kugesan1105 Oct 17, 2025
57d7d1c
Add TODO comment for connecting function arguments to their declarati…
kugesan1105 Oct 17, 2025
628de4b
Refactor test commands in GitHub Actions workflow for improved clarit…
kugesan1105 Oct 17, 2025
84b9659
Add type checker test for member access and symbol resolution in `sym…
kugesan1105 Oct 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
exclude = fixtures, __jac_gen__, build, examples, venv, vendor, generated
exclude = fixtures, build, examples, venv, vendor, generated
plugins = flake8_import_order, flake8_comprehensions, flake8_bugbear, flake8_annotations, pep8_naming, flake8_simplify
max-line-length = 120
ignore = E203, W503, ANN101, ANN102
4 changes: 2 additions & 2 deletions jac/jaclang/compiler/passes/main/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from ..transform import Alert, Transform # noqa: I100
from .annex_pass import JacAnnexPass # noqa: I100
from .sym_tab_build_pass import SymTabBuildPass, UniPass # noqa: I100
from .def_use_pass import DefUsePass # noqa: I100
from .semantic_analysis_pass import SemanticAnalysisPass # noqa: I100
from .sem_def_match_pass import SemDefMatchPass # noqa: I100
from .import_pass import JacImportDepsPass # noqa: I100
from .def_impl_match_pass import DeclImplMatchPass # noqa: I100
Expand All @@ -24,8 +24,8 @@
"JacImportDepsPass",
"TypeCheckPass",
"SymTabBuildPass",
"SemanticAnalysisPass",
"DeclImplMatchPass",
"DefUsePass",
"SemDefMatchPass",
"PyastBuildPass",
"PyastGenPass",
Expand Down
71 changes: 71 additions & 0 deletions jac/jaclang/compiler/passes/main/semantic_analysis_pass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Jac Semantic Analysis Pass."""

import ast as ast3

import jaclang.compiler.unitree as uni
from jaclang.compiler.constant import Tokens as Tok
from jaclang.compiler.passes import UniPass


class SemanticAnalysisPass(UniPass):
"""Jac Semantic Analysis Pass."""

def enter_archetype(self, node: uni.Archetype) -> None:

def inform_from_walker(node: uni.UniNode) -> None:
for i in (
node.get_all_sub_nodes(uni.VisitStmt)
+ node.get_all_sub_nodes(uni.DisengageStmt)
+ node.get_all_sub_nodes(uni.EdgeOpRef)
+ node.get_all_sub_nodes(uni.EventSignature)
+ node.get_all_sub_nodes(uni.TypedCtxBlock)
):
i.from_walker = True

if node.arch_type.name == Tok.KW_WALKER:
inform_from_walker(node)
for i in self.get_all_sub_nodes(node, uni.Ability):
if isinstance(i.body, uni.ImplDef):
inform_from_walker(i.body)

# ------------context update methods---------------------------
def _update_ctx(self, node: uni.UniNode) -> None:
if isinstance(node, uni.AtomTrailer):
self._change_atom_trailer_ctx(node)
elif isinstance(node, uni.AstSymbolNode):
node.sym_tab.update_py_ctx_for_def(node)
else:
self.log_error(f"Invalid target for context update: {type(node).__name__}")

def enter_has_var(self, node: uni.HasVar) -> None:
if isinstance(node.parent, uni.ArchHas):
node.sym_tab.update_py_ctx_for_def(node)
else:
self.ice("HasVar should be under ArchHas")

def enter_param_var(self, node: uni.ParamVar) -> None:
node.sym_tab.update_py_ctx_for_def(node)

def enter_assignment(self, node: uni.Assignment) -> None:
for target in node.target:
self._update_ctx(target)

def enter_in_for_stmt(self, node: uni.InForStmt) -> None:
self._update_ctx(node.target)

def enter_expr_as_item(self, node: uni.ExprAsItem) -> None:
if node.alias:
self._update_ctx(node.alias)

def enter_inner_compr(self, node: uni.InnerCompr) -> None:
self._update_ctx(node.target)

# ----------------------- Utilities -------------------------

def _change_atom_trailer_ctx(self, node: uni.AtomTrailer) -> None:
"""Mark final element in trailer chain as a Store context."""
last = node.right
if isinstance(last, uni.AtomExpr):
last.name_spec.py_ctx_func = ast3.Store
if isinstance(last.name_spec, uni.AstSymbolNode):
last.name_spec.py_ctx_func = ast3.Store
58 changes: 57 additions & 1 deletion jac/jaclang/compiler/passes/main/sym_tab_build_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,15 @@ def exit_global_vars(self, node: uni.GlobalVars) -> None:
if isinstance(j, uni.AstSymbolNode):
j.sym_tab.def_insert(j, access_spec=node, single_decl="global var")
else:
self.ice("Expected name type for globabl vars")
self.ice("Expected name type for global vars")

def exit_assignment(self, node: uni.Assignment) -> None:
for i in node.target:
if isinstance(i, uni.AstSymbolNode):
if (sym := i.sym_tab.lookup(i.sym_name, deep=False)) is None:
i.sym_tab.def_insert(i, single_decl="local var")
else:
sym.add_use(i.name_spec)

def enter_test(self, node: uni.Test) -> None:
self.push_scope_and_link(node)
Expand Down Expand Up @@ -149,6 +157,54 @@ def enter_enum(self, node: uni.Enum) -> None:
assert node.parent_scope is not None
node.parent_scope.def_insert(node, access_spec=node, single_decl="enum")

def enter_has_var(self, node: uni.HasVar) -> None:
if isinstance(node.parent, uni.ArchHas):
node.sym_tab.def_insert(
node, single_decl="has var", access_spec=node.parent
)

def enter_param_var(self, node: uni.ParamVar) -> None:
node.sym_tab.def_insert(node, single_decl="param")

def exit_atom_trailer(self, node: uni.AtomTrailer) -> None:
"""Handle attribute access for self member assignments."""
if not self._is_self_member_assignment(node):
return

chain = node.as_attr_list
ability = node.find_parent_of_type(uni.Ability)

# Register the attribute in the archetype's symbol table
# Example: self.attr = value → add 'attr' to archetype.sym_tab
if ability and ability.method_owner:
archetype = ability.method_owner
if isinstance(archetype, uni.Archetype):
archetype.sym_tab.def_insert(chain[1], access_spec=archetype)

def _is_self_member_assignment(self, node: uni.AtomTrailer) -> bool:
"""Check if the node represents a simple `self.attr = value` assignment."""
# Must be inside an assignment as the target
if not (node.parent and isinstance(node.parent, uni.Assignment)):
return False

if node != node.parent.target[0]: # TODO: Support multiple assignment targets
return False

chain = node.as_attr_list

# Must be a direct self attribute (no nested attributes)
if len(chain) != 2 or chain[0].sym_name != "self":
return False

# Must be inside a non-static, non-class instance method
ability = node.find_parent_of_type(uni.Ability)
return (
ability is not None
and ability.is_method
and not ability.is_static
and not ability.is_cls_method
)

def exit_enum(self, node: uni.Enum) -> None:
self.pop_scope()

Expand Down
24 changes: 24 additions & 0 deletions jac/jaclang/compiler/passes/main/tests/fixtures/symtab_build.jac
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
obj Person{
has age:int;

def greet{
self.name = "John";
}

static def create_person{
self.first_name = "John";
self.first_name =12;
}

@classmethod
def class_info{
self.type = "Human";
print("This is the Person class");
}
}


with entry{
alice = Person(age=30);
alice.age = '909'; # <-- Error
}
34 changes: 34 additions & 0 deletions jac/jaclang/compiler/passes/main/tests/test_checker_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,40 @@ def test_checker_cat_is_animal(self) -> None:
^^^^^^^^^^
""", program.errors_had[0].pretty_print())

def test_checker_member_access(self) -> None:
path = self.fixture_abs_path("symtab_build.jac")
program = JacProgram()
mod = program.compile(path)
TypeCheckPass(ir_in=mod, prog=program)
self.assertEqual(
len(mod.sym_tab.names_in_scope.values()),
2,
)
mod_scope_symbols = ['Symbol(alice', 'Symbol(Person']
for sym in mod_scope_symbols:
self.assertIn(sym, str(mod.sym_tab.names_in_scope.values()))
self.assertEqual(
len(mod.sym_tab.kid_scope[0].names_in_scope.values()),
5,
)
kid_scope_symbols = [
'Symbol(age',
'Symbol(greet',
'Symbol(name,',
'Symbol(create_person',
'Symbol(class_info',
]
for sym in kid_scope_symbols:
self.assertIn(sym, str(mod.sym_tab.kid_scope[0].names_in_scope.values()))
age_sym = mod.sym_tab.kid_scope[0].lookup("age")
assert age_sym is not None
self.assertIn('(NAME, age, 23:11 - 23:14)', str(age_sym.uses))
self.assertEqual(len(program.errors_had), 1)
self._assert_error_pretty_found("""
alice.age = '909'; # <-- Error
^^^^^^^^^^^^^^^^^^
""", program.errors_had[0].pretty_print())

def test_checker_import_missing_module(self) -> None:
path = self.fixture_abs_path("checker_import_missing_module.jac")
program = JacProgram()
Expand Down
35 changes: 0 additions & 35 deletions jac/jaclang/compiler/passes/main/tests/test_def_use_pass.py

This file was deleted.

9 changes: 9 additions & 0 deletions jac/jaclang/compiler/passes/main/type_checker_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,12 @@ def exit_func_call(self, node: uni.FuncCall) -> None:
# 1. Function Existence & Callable Validation
# 2. Argument Matching(count, types, names)
self.evaluator.get_type_of_expression(node)

def exit_return_stmt(self, node: uni.ReturnStmt) -> None:
"""Handle the return statement node."""
if node.expr:
self.evaluator.get_type_of_expression(node.expr)

def exit_formatted_value(self, node: uni.FormattedValue) -> None:
"""Handle the formatted value node."""
self.evaluator.get_type_of_expression(node.format_part)
7 changes: 3 additions & 4 deletions jac/jaclang/compiler/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
Alert,
CFGBuildPass,
DeclImplMatchPass,
DefUsePass,
JacAnnexPass,
JacImportDepsPass,
PreDynamoPass,
Expand All @@ -24,6 +23,7 @@
PyastBuildPass,
PyastGenPass,
SemDefMatchPass,
SemanticAnalysisPass,
SymTabBuildPass,
Transform,
TypeCheckPass,
Expand All @@ -42,7 +42,7 @@
ir_gen_sched = [
SymTabBuildPass,
DeclImplMatchPass,
DefUsePass,
SemanticAnalysisPass,
SemDefMatchPass,
CFGBuildPass,
]
Expand Down Expand Up @@ -153,8 +153,7 @@ def build(
"""Convert a Jac file to an AST."""
mod_targ = self.compile(file_path, use_str, type_check=type_check)
JacImportDepsPass(ir_in=mod_targ, prog=self)
for mod in self.mod.hub.values():
DefUsePass(mod, prog=self)
SemanticAnalysisPass(ir_in=mod_targ, prog=self)
return mod_targ

def run_schedule(
Expand Down
Loading