Skip to content
This repository has been archived by the owner on Sep 12, 2024. It is now read-only.

Commit

Permalink
Merge pull request #534 from Jaseci-Labs/thakee-check-syntax
Browse files Browse the repository at this point in the history
check syntax no more explicitly need to call assertXXX
  • Loading branch information
marsninja authored Aug 5, 2024
2 parents ea7dbf9 + 3123adf commit 9ae0149
Show file tree
Hide file tree
Showing 13 changed files with 191 additions and 62 deletions.
6 changes: 3 additions & 3 deletions examples/manual_code/circle.jac
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
6 changes: 3 additions & 3 deletions examples/manual_code/circle_clean_tests.jac
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
6 changes: 3 additions & 3 deletions examples/manual_code/circle_pure.test.jac
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 4 additions & 4 deletions examples/reference/check_statements.jac
Original file line number Diff line number Diff line change
@@ -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;
}
6 changes: 3 additions & 3 deletions examples/reference/tests.jac
Original file line number Diff line number Diff line change
@@ -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__ {
Expand Down
172 changes: 150 additions & 22 deletions jaclang/compiler/passes/main/pyast_gen_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -2139,33 +2140,160 @@ 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.
@dataclass
class CheckNodeIsinstanceCallResult:
isit: bool = False
inst: ast3.AST | None = None
clss: ast3.AST | None = None

# This will check if a node is `isinstance(<expr>, <expr>)`, we're
# using a function because it's reusable to check not isinstance(<expr>, <expr>).
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(<expr>), 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
opty: ast.Token = expr.ops[0]

optype2fn = {
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 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 <expr> is None.
if opty.name == Tok.KW_IS and isinstance(expr.rights[0], ast.Null):
assert_func_name = "assertIsNone"
assert_args_list.pop()

# Override for <expr> is not None.
elif opty.name == Tok.KW_ISN 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(<expr>, <expr>)' is called.
elif (
isinstance(node.target, ast.UnaryExpr)
and isinstance(node.target, ast.UnaryExpr)
and isinstance(node.target.operand, ast.FuncCall)
and isinstance(node.target.operand, ast.UnaryExpr)
):
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,
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:
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.
Expand Down
6 changes: 3 additions & 3 deletions jaclang/langserve/tests/fixtures/circle.jac
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
6 changes: 3 additions & 3 deletions jaclang/langserve/tests/fixtures/circle_err.jac
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
6 changes: 3 additions & 3 deletions jaclang/langserve/tests/fixtures/circle_pure.test.jac
Original file line number Diff line number Diff line change
@@ -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;
}
9 changes: 5 additions & 4 deletions jaclang/langserve/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -222,7 +222,7 @@ def test_sem_tokens(self) -> None:
),
(
"<JacSemTokenType.FUNCTION: 12>, <JacSemTokenModifier.DECLARATION: 1>,",
9,
6,
),
("<JacSemTokenType.METHOD: 13>, <JacSemTokenModifier.DECLARATION: 1>", 6),
("<JacSemTokenType.ENUM: 3>, <JacSemTokenModifier.DECLARATION: 1>,", 4),
Expand All @@ -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)

Expand Down Expand Up @@ -343,8 +344,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)))
Expand Down
Loading

0 comments on commit 9ae0149

Please sign in to comment.