From a7196d6baba447e8fc5b78fb8c2957418b3f9572 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Sat, 27 Jul 2024 00:52:31 +0530 Subject: [PATCH 1/3] check syntax no more explicitly need to call assertXXX --- examples/manual_code/circle.jac | 6 +- examples/manual_code/circle_clean_tests.jac | 6 +- examples/manual_code/circle_pure.test.jac | 6 +- examples/reference/check_statements.jac | 8 +- examples/reference/tests.jac | 6 +- .../compiler/passes/main/pyast_gen_pass.py | 175 +++++++++++++++--- jaclang/langserve/tests/fixtures/circle.jac | 6 +- .../langserve/tests/fixtures/circle_err.jac | 6 +- .../tests/fixtures/circle_pure.test.jac | 6 +- jaclang/tests/fixtures/abc.jac | 6 +- jaclang/tests/fixtures/maxfail_run_test.jac | 8 +- jaclang/tests/fixtures/run_test.jac | 8 +- 12 files changed, 190 insertions(+), 57 deletions(-) diff --git a/examples/manual_code/circle.jac b/examples/manual_code/circle.jac index e0dc0c365..755fab2bf 100644 --- a/examples/manual_code/circle.jac +++ b/examples/manual_code/circle.jac @@ -63,15 +63,15 @@ with entry:__main__ { glob expected_area = 78.53981633974483; test calc_area { - check assertAlmostEqual(calculate_area(RAD), expected_area); + check almostEqual(calculate_area(RAD), expected_area); } test circle_area { c = Circle(RAD); - check assertAlmostEqual(c.area(), expected_area); + check almostEqual(c.area(), expected_area); } test circle_type { c = Circle(RAD); - check assertEqual(c.shape_type, ShapeType.CIRCLE); + check c.shape_type == ShapeType.CIRCLE; } diff --git a/examples/manual_code/circle_clean_tests.jac b/examples/manual_code/circle_clean_tests.jac index 66fcc4282..c9626eede 100644 --- a/examples/manual_code/circle_clean_tests.jac +++ b/examples/manual_code/circle_clean_tests.jac @@ -3,15 +3,15 @@ include:jac circle_clean; glob expected_area = 78.53981633974483; test { - check assertAlmostEqual(calculate_area(RAD), expected_area); + check almostEqual(calculate_area(RAD), expected_area); } test { c = Circle(RAD); - check assertAlmostEqual(c.area(), expected_area); + check almostEqual(c.area(), expected_area); } test { c = Circle(RAD); - check assertEqual(c.shape_type, ShapeType.CIRCLE); + check c.shape_type == ShapeType.CIRCLE; } diff --git a/examples/manual_code/circle_pure.test.jac b/examples/manual_code/circle_pure.test.jac index 175f67c61..c30518578 100644 --- a/examples/manual_code/circle_pure.test.jac +++ b/examples/manual_code/circle_pure.test.jac @@ -1,15 +1,15 @@ glob expected_area = 78.53981633974483; test a1 { - check assertAlmostEqual(calculate_area(RAD), expected_area); + check almostEqual(calculate_area(RAD), expected_area); } test a2 { c = Circle(RAD); - check assertAlmostEqual(c.area(), expected_area); + check almostEqual(c.area(), expected_area); } test a3 { c = Circle(RAD); - check assertEqual(c.shape_type, ShapeType.CIRCLE); + check c.shape_type == ShapeType.CIRCLE; } diff --git a/examples/reference/check_statements.jac b/examples/reference/check_statements.jac index f0750c1bc..e6d6ca33c 100644 --- a/examples/reference/check_statements.jac +++ b/examples/reference/check_statements.jac @@ -1,17 +1,17 @@ glob a = 5, b = 2; test test1 { - check assertAlmostEqual(a, 6); + check almostEqual(a, 6); } test test2 { - check assertTrue(a != b); + check a != b; } test test3 { - check assertIn("d", "abc"); + check "d" in "abc"; } test test4 { - check assertEqual(a - b, 3); + check a - b == 3; } \ No newline at end of file diff --git a/examples/reference/tests.jac b/examples/reference/tests.jac index 9e6ecd26f..996ab07a8 100644 --- a/examples/reference/tests.jac +++ b/examples/reference/tests.jac @@ -1,13 +1,13 @@ test test1 { - check assertAlmostEqual(4.99999, 4.99999); + check almostEqual(4.99999, 4.99999); } test test2 { - check assertEqual(5, 5); + check 5 == 5; } test test3 { - check assertIn("e", "qwerty"); + check "e" in "qwerty"; } with entry:__main__ { diff --git a/jaclang/compiler/passes/main/pyast_gen_pass.py b/jaclang/compiler/passes/main/pyast_gen_pass.py index 1f668cd4b..f418bebb2 100644 --- a/jaclang/compiler/passes/main/pyast_gen_pass.py +++ b/jaclang/compiler/passes/main/pyast_gen_pass.py @@ -2139,33 +2139,166 @@ def exit_check_stmt(self, node: ast.CheckStmt) -> None: target: ExprType, """ + # TODO: Here is the list of assertions which are not implemented instead a simpler version of them will work. + # ie. [] == [] will be assertEqual instead of assertListEqual. However I don't think this is needed since it can + # only detected if both operand are compile time literal list or type inferable. + # + # assertAlmostEqual + # assertNotAlmostEqual + # assertSequenceEqual + # assertListEqual + # assertTupleEqual + # assertSetEqual + # assertDictEqual + # assertCountEqual + # assertMultiLineEqual + # assertRaisesRegex + # assertWarnsRegex + # assertRegex + # assertNotRegex + + # The return type "struct" for the bellow check_node_isinstance_call. + class CheckNodeIsinstanceCallResult: + def __init__( + self, + isit: bool = False, + inst: ast3.AST | None = None, + clss: ast3.AST | None = None, + ) -> None: + self.isit: bool = isit + self.inst: ast3.AST | None = inst + self.clss: ast3.AST | None = clss + + # This will check if a node is `isinstance(, )`, we're + # using a function because it's reusable to check not isinstance(, ). + def check_node_isinstance_call( + node: ast.FuncCall, + ) -> CheckNodeIsinstanceCallResult: + + # Ensure the type of the FuncCall node is SubNodeList[Expr] + # since the type can be: Optional[SubNodeList[Expr | KWPair]]. + if not ( + node.params is not None + and len(node.params.items) == 2 + and isinstance(node.params.items[0], ast.Expr) + and isinstance(node.params.items[1], ast.Expr) + ): + return CheckNodeIsinstanceCallResult() + + func = node.target.gen.py_ast[0] + if not (isinstance(func, ast3.Name) and func.id == "isinstance"): + return CheckNodeIsinstanceCallResult() + + return CheckNodeIsinstanceCallResult( + True, + node.params.items[0].gen.py_ast[0], + node.params.items[1].gen.py_ast[0], + ) + + # By default the check expression will become assertTrue(), unless any pattern detected. + assert_func_name = "assertTrue" + assert_args_list = node.target.gen.py_ast + + # Compare operations. Note that We're only considering the compare + # operation with a single operation ie. a < b < c is ignored here. + if ( + isinstance(node.target, ast.CompareExpr) + and isinstance(node.target.gen.py_ast[0], ast3.Compare) + and len(node.target.ops) == 1 + ): + expr: ast.CompareExpr = node.target + pyexpr: ast3.Compare = node.target.gen.py_ast[0] + op_ty: type[ast3.cmpop] = type(pyexpr.ops[0]) + + optype2fn = { + ast3.Eq: "assertEqual", + ast3.NotEq: "assertNotEqual", + ast3.Lt: "assertLess", + ast3.LtE: "assertLessEqual", + ast3.Gt: "assertGreater", + ast3.GtE: "assertGreaterEqual", + ast3.In: "assertIn", + ast3.NotIn: "assertNotIn", + ast3.Is: "assertIs", + ast3.IsNot: "assertIsNot", + } + + if op_ty in optype2fn: + assert_func_name = optype2fn[op_ty] + assert_args_list = [ + expr.left.gen.py_ast[0], + expr.rights[0].gen.py_ast[0], + ] + + # Override for is None. + if op_ty == ast3.Is and isinstance(expr.rights[0], ast.Null): + assert_func_name = "assertIsNone" + assert_args_list.pop() + + # Override for is not None. + elif op_ty == ast3.IsNot and isinstance(expr.rights[0], ast.Null): + assert_func_name = "assertIsNotNone" + assert_args_list.pop() + + # Check if 'isinstance' is called. + elif isinstance(node.target, ast.FuncCall) and isinstance( + node.target.gen.py_ast[0], ast3.Call + ): + res = check_node_isinstance_call(node.target) + if res.isit: + # These assertions will make mypy happy. + assert isinstance(res.inst, ast3.AST) + assert isinstance(res.clss, ast3.AST) + assert_func_name = "assertIsInstance" + assert_args_list = [res.inst, res.clss] + + # Check if 'not isinstance(, )' is called. + elif ( + isinstance(node.target, ast.UnaryExpr) + and isinstance(node.target.gen.py_ast[0], ast3.UnaryOp) + and isinstance(node.target.operand, ast.FuncCall) + and isinstance(node.target.operand.gen.py_ast[0], ast3.UnaryOp) + ): + res = check_node_isinstance_call(node.target.operand) + if res.isit: + # These assertions will make mypy happy. + assert isinstance(res.inst, ast3.AST) + assert isinstance(res.clss, ast3.AST) + assert_func_name = "assertIsNotInstance" + assert_args_list = [res.inst, res.clss] + + # NOTE That the almost equal is NOT a builtin function of jaclang and won't work outside of the + # check statement. And we're hacking the node here. Not sure if this is a hacky workaround to support + # the almost equal functionality (snice there is no almost equal operator in jac and never needed ig.). + + # Check if 'almostEqual' is called. if isinstance(node.target, ast.FuncCall) and isinstance( node.target.gen.py_ast[0], ast3.Call ): func = node.target.target.gen.py_ast[0] - if isinstance(func, ast3.Name): - new_func: ast3.expr = self.sync( - ast3.Attribute( - value=self.sync(ast3.Name(id="_jac_check", ctx=ast3.Load())), - attr=func.id, - ctx=ast3.Load(), - ) - ) - node.target.gen.py_ast[0].func = new_func - node.gen.py_ast = [ - self.sync( - ast3.Expr( - value=node.target.gen.py_ast[0], - ) - ) - ] - return - self.error( - "For now, check statements must be function calls " - "in the style of assertTrue(), assertEqual(), etc.", - node, + if isinstance(func, ast3.Name) and func.id == "almostEqual": + assert_func_name = "assertAlmostEqual" + assert_args_list = [] + if node.target.params is not None: + for param in node.target.params.items: + assert_args_list.append(param.gen.py_ast[0]) + + # assert_func_expr = "_jac_check.assertXXX" + assert_func_expr: ast3.Attribute = self.sync( + ast3.Attribute( + value=self.sync(ast3.Name(id="_jac_check", ctx=ast3.Load())), + attr=assert_func_name, + ctx=ast3.Load(), + ) ) + # assert_call_expr = "(_jac_check.assertXXX)(args)" + assert_call_expr: ast3.Call = self.sync( + ast3.Call(func=assert_func_expr, args=assert_args_list, keywords=[]) + ) + + node.gen.py_ast = [self.sync(ast3.Expr(assert_call_expr))] + def exit_ctrl_stmt(self, node: ast.CtrlStmt) -> None: """Sub objects. diff --git a/jaclang/langserve/tests/fixtures/circle.jac b/jaclang/langserve/tests/fixtures/circle.jac index e0dc0c365..755fab2bf 100644 --- a/jaclang/langserve/tests/fixtures/circle.jac +++ b/jaclang/langserve/tests/fixtures/circle.jac @@ -63,15 +63,15 @@ with entry:__main__ { glob expected_area = 78.53981633974483; test calc_area { - check assertAlmostEqual(calculate_area(RAD), expected_area); + check almostEqual(calculate_area(RAD), expected_area); } test circle_area { c = Circle(RAD); - check assertAlmostEqual(c.area(), expected_area); + check almostEqual(c.area(), expected_area); } test circle_type { c = Circle(RAD); - check assertEqual(c.shape_type, ShapeType.CIRCLE); + check c.shape_type == ShapeType.CIRCLE; } diff --git a/jaclang/langserve/tests/fixtures/circle_err.jac b/jaclang/langserve/tests/fixtures/circle_err.jac index 5b6e04880..29736a7c0 100644 --- a/jaclang/langserve/tests/fixtures/circle_err.jac +++ b/jaclang/langserve/tests/fixtures/circle_err.jac @@ -59,15 +59,15 @@ print(f"Area of a {c.shape_type.value} with radius {RAD} using class: {c.area()} glob expected_area = 78.53981633974483; test calc_area { - check assertAlmostEqual(calculate_area(RAD), expected_area); + check almostEqual(calculate_area(RAD), expected_area); } test circle_area { c = Circle(RAD); - check assertAlmostEqual(c.area(), expected_area); + check almostEqual(c.area(), expected_area); } test circle_type { c = Circle(RAD); - check assertEqual(c.shape_type, ShapeType.CIRCLE); + check c.shape_type == ShapeType.CIRCLE; } diff --git a/jaclang/langserve/tests/fixtures/circle_pure.test.jac b/jaclang/langserve/tests/fixtures/circle_pure.test.jac index 175f67c61..c30518578 100644 --- a/jaclang/langserve/tests/fixtures/circle_pure.test.jac +++ b/jaclang/langserve/tests/fixtures/circle_pure.test.jac @@ -1,15 +1,15 @@ glob expected_area = 78.53981633974483; test a1 { - check assertAlmostEqual(calculate_area(RAD), expected_area); + check almostEqual(calculate_area(RAD), expected_area); } test a2 { c = Circle(RAD); - check assertAlmostEqual(c.area(), expected_area); + check almostEqual(c.area(), expected_area); } test a3 { c = Circle(RAD); - check assertEqual(c.shape_type, ShapeType.CIRCLE); + check c.shape_type == ShapeType.CIRCLE; } diff --git a/jaclang/tests/fixtures/abc.jac b/jaclang/tests/fixtures/abc.jac index e0dc0c365..755fab2bf 100644 --- a/jaclang/tests/fixtures/abc.jac +++ b/jaclang/tests/fixtures/abc.jac @@ -63,15 +63,15 @@ with entry:__main__ { glob expected_area = 78.53981633974483; test calc_area { - check assertAlmostEqual(calculate_area(RAD), expected_area); + check almostEqual(calculate_area(RAD), expected_area); } test circle_area { c = Circle(RAD); - check assertAlmostEqual(c.area(), expected_area); + check almostEqual(c.area(), expected_area); } test circle_type { c = Circle(RAD); - check assertEqual(c.shape_type, ShapeType.CIRCLE); + check c.shape_type == ShapeType.CIRCLE; } diff --git a/jaclang/tests/fixtures/maxfail_run_test.jac b/jaclang/tests/fixtures/maxfail_run_test.jac index f41b9a1e9..19fc69d14 100644 --- a/jaclang/tests/fixtures/maxfail_run_test.jac +++ b/jaclang/tests/fixtures/maxfail_run_test.jac @@ -1,17 +1,17 @@ glob x = 5, y = 2; test a { - check assertAlmostEqual(5, x); + check almostEqual(5, x); } test b { - check assertIn("l", "llm"); + check "l" in "llm"; } test c { - check assertEqual(x - y, 3); + check x - y == 3; } test d { - check assertEqual(1, 2); + check 1 == 2; } diff --git a/jaclang/tests/fixtures/run_test.jac b/jaclang/tests/fixtures/run_test.jac index 9bf2512d8..f883aa6e2 100644 --- a/jaclang/tests/fixtures/run_test.jac +++ b/jaclang/tests/fixtures/run_test.jac @@ -1,17 +1,17 @@ glob a = 5, b = 2; test t1 { - check assertAlmostEqual(a, 6); + check almostEqual(a, 6); } test t2 { - check assertTrue(a != b); + check a != b; } test t3 { - check assertIn("d", "abc"); + check "d" in "abc"; } test t4 { - check assertEqual(a - b, 3); + check a - b == 3; } From 16fa5bf9c71369276d6678e0716130c904be36b7 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Mon, 29 Jul 2024 23:22:44 +0530 Subject: [PATCH 2/3] fixes for the failed build --- jaclang/langserve/tests/test_server.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/jaclang/langserve/tests/test_server.py b/jaclang/langserve/tests/test_server.py index f2214d6f9..b9edb9381 100644 --- a/jaclang/langserve/tests/test_server.py +++ b/jaclang/langserve/tests/test_server.py @@ -170,7 +170,7 @@ def test_test_annex(self) -> None: lsp.lsp._workspace = workspace circle_file = uris.from_fs_path(self.fixture_abs_path("circle_pure.test.jac")) lsp.deep_check(circle_file) - pos = lspt.Position(13, 29) + pos = lspt.Position(13, 21) self.assertIn( "shape_type: circle_pure.ShapeType", lsp.get_hover_info(circle_file, pos).contents.value, @@ -222,7 +222,7 @@ def test_sem_tokens(self) -> None: ), ( ", ,", - 9, + 6, ), (", ", 6), (", ,", 4), @@ -232,6 +232,7 @@ def test_sem_tokens(self) -> None: 3, ), ] + print(str(sem_list)) for token_type, expected_count in expected_counts: self.assertEqual(str(sem_list).count(token_type), expected_count) @@ -288,8 +289,8 @@ def test_go_to_reference(self) -> None: lsp.deep_check(circle_file) test_cases = [ (47, 12, ["circle.jac:47:8-47:14", "69:8-69:14", "74:8-74:14"]), - (54, 66, ["54:62-54:76", "65:28-65:42"]), - (62, 14, ["65:49-65:62", "70:38-70:51"]), + (54, 66, ["54:62-54:76", "65:22-65:36"]), + (62, 14, ["65:43-65:56", "70:32-70:45"]), ] for line, char, expected_refs in test_cases: references = str(lsp.get_references(circle_file, lspt.Position(line, char))) From 2e951c2cba285ce15d0edbaec80e25d8cc1b458a Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Fri, 2 Aug 2024 19:21:23 +0530 Subject: [PATCH 3/3] check node jac first --- .../compiler/passes/main/pyast_gen_pass.py | 53 +++++++++---------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/jaclang/compiler/passes/main/pyast_gen_pass.py b/jaclang/compiler/passes/main/pyast_gen_pass.py index f418bebb2..b30777b56 100644 --- a/jaclang/compiler/passes/main/pyast_gen_pass.py +++ b/jaclang/compiler/passes/main/pyast_gen_pass.py @@ -6,6 +6,7 @@ import ast as ast3 import textwrap +from dataclasses import dataclass from typing import Optional, Sequence, TypeVar import jaclang.compiler.absyntree as ast @@ -2158,16 +2159,11 @@ def exit_check_stmt(self, node: ast.CheckStmt) -> None: # assertNotRegex # The return type "struct" for the bellow check_node_isinstance_call. + @dataclass class CheckNodeIsinstanceCallResult: - def __init__( - self, - isit: bool = False, - inst: ast3.AST | None = None, - clss: ast3.AST | None = None, - ) -> None: - self.isit: bool = isit - self.inst: ast3.AST | None = inst - self.clss: ast3.AST | None = clss + isit: bool = False + inst: ast3.AST | None = None + clss: ast3.AST | None = None # This will check if a node is `isinstance(, )`, we're # using a function because it's reusable to check not isinstance(, ). @@ -2207,36 +2203,35 @@ def check_node_isinstance_call( and len(node.target.ops) == 1 ): expr: ast.CompareExpr = node.target - pyexpr: ast3.Compare = node.target.gen.py_ast[0] - op_ty: type[ast3.cmpop] = type(pyexpr.ops[0]) + opty: ast.Token = expr.ops[0] optype2fn = { - ast3.Eq: "assertEqual", - ast3.NotEq: "assertNotEqual", - ast3.Lt: "assertLess", - ast3.LtE: "assertLessEqual", - ast3.Gt: "assertGreater", - ast3.GtE: "assertGreaterEqual", - ast3.In: "assertIn", - ast3.NotIn: "assertNotIn", - ast3.Is: "assertIs", - ast3.IsNot: "assertIsNot", + Tok.EE.name: "assertEqual", + Tok.NE.name: "assertNotEqual", + Tok.LT.name: "assertLess", + Tok.LTE.name: "assertLessEqual", + Tok.GT.name: "assertGreater", + Tok.GTE.name: "assertGreaterEqual", + Tok.KW_IN.name: "assertIn", + Tok.KW_NIN.name: "assertNotIn", + Tok.KW_IS.name: "assertIs", + Tok.KW_ISN.name: "assertIsNot", } - if op_ty in optype2fn: - assert_func_name = optype2fn[op_ty] + if opty.name in optype2fn: + assert_func_name = optype2fn[opty.name] assert_args_list = [ expr.left.gen.py_ast[0], expr.rights[0].gen.py_ast[0], ] # Override for is None. - if op_ty == ast3.Is and isinstance(expr.rights[0], ast.Null): + if opty.name == Tok.KW_IS and isinstance(expr.rights[0], ast.Null): assert_func_name = "assertIsNone" assert_args_list.pop() # Override for is not None. - elif op_ty == ast3.IsNot and isinstance(expr.rights[0], ast.Null): + elif opty.name == Tok.KW_ISN and isinstance(expr.rights[0], ast.Null): assert_func_name = "assertIsNotNone" assert_args_list.pop() @@ -2255,9 +2250,9 @@ def check_node_isinstance_call( # Check if 'not isinstance(, )' is called. elif ( isinstance(node.target, ast.UnaryExpr) - and isinstance(node.target.gen.py_ast[0], ast3.UnaryOp) + and isinstance(node.target, ast.UnaryExpr) and isinstance(node.target.operand, ast.FuncCall) - and isinstance(node.target.operand.gen.py_ast[0], ast3.UnaryOp) + and isinstance(node.target.operand, ast.UnaryExpr) ): res = check_node_isinstance_call(node.target.operand) if res.isit: @@ -2275,8 +2270,8 @@ def check_node_isinstance_call( if isinstance(node.target, ast.FuncCall) and isinstance( node.target.gen.py_ast[0], ast3.Call ): - func = node.target.target.gen.py_ast[0] - if isinstance(func, ast3.Name) and func.id == "almostEqual": + func = node.target.target + if isinstance(func, ast.Name) and func.value == "almostEqual": assert_func_name = "assertAlmostEqual" assert_args_list = [] if node.target.params is not None: