diff --git a/jac/examples/reference/architypes.py b/jac/examples/reference/architypes.py index 4aa77ca03b..79ba882d7e 100644 --- a/jac/examples/reference/architypes.py +++ b/jac/examples/reference/architypes.py @@ -4,12 +4,12 @@ def print_base_classes(cls: type) -> type: print( - f"Base classes of {cls.__name__}: {List([c.__name__ for c in cls.__bases__])}" + f"Base classes of {cls.__name__}: {JacList([c.__name__ for c in cls.__bases__])}" ) return cls -class Animal(Obj): +class Animal: pass @@ -18,18 +18,18 @@ class Domesticated(Obj): @print_base_classes -class Pet(Animal, Domesticated, Obj): +class Pet(Animal, Domesticated, Node): pass -class Person(Animal, Obj): +class Person(Animal, Walker): pass -class Feeder(Person, Obj): +class Feeder(Person, Walker): pass @print_base_classes -class Zoologist(Feeder, Obj): +class Zoologist(Feeder, Walker): pass diff --git a/jac/examples/reference/builtin_types.py b/jac/examples/reference/builtin_types.py index 2fb098aaf0..4fa5767788 100644 --- a/jac/examples/reference/builtin_types.py +++ b/jac/examples/reference/builtin_types.py @@ -1,8 +1,8 @@ -from jaclang import List +from jaclang import JacList a = 9.2 b = 44 -c = List([2, 4, 6, 10]) +c = JacList([2, 4, 6, 10]) d = {"name": "john", "age": 28} e = ("jaseci", 5, 4, 14) f = True diff --git a/jac/jaclang/__init__.py b/jac/jaclang/__init__.py index 7f6923841c..4a9688f88b 100644 --- a/jac/jaclang/__init__.py +++ b/jac/jaclang/__init__.py @@ -13,8 +13,10 @@ Generic, Tuple, Type, + TypeGuard, TypeVar, override, + cast, ) from jaclang.plugin.builtin import dotgen, jid, jobj # noqa: F401 @@ -30,7 +32,7 @@ "Walker", "Node", "Edge", - "List", + "JacList", "EdgeDir", "Root", # Decorators. @@ -60,6 +62,7 @@ plugin_manager.load_setuptools_entrypoints("jac") T = TypeVar("T") +S = TypeVar("S") # S is a subtype of T. # ---------------------------------------------------------------------------- # Meta classes. @@ -74,10 +77,12 @@ # Reference: https://stackoverflow.com/a/9639512/10846399 # class _ArchiTypeBase: - pass + def __init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 + """Initialize Jac architype base.""" -class MetaCommon(ABCMeta): + +class JacMeta(ABCMeta): """Common metaclass for Jac types.""" def __new__( # noqa: D102 @@ -85,8 +90,7 @@ def __new__( # noqa: D102 name: str, bases: Tuple[Type, ...], dct: Dict[str, Any], - make_func: Callable[[list, list], Callable[[type], type]], - ) -> "MetaCommon": + ) -> "JacMeta": # We have added this "__init__" to the jac base class just to make the type checkers happy. # Actually the dataclass decorator will create an __init__ function and assign it here bellow. @@ -102,55 +106,25 @@ def __new__( # noqa: D102 inst = super().__new__(cls, name, bases, dct) inst = dataclass(eq=False)(inst) # type: ignore [arg-type, assignment] - inst = make_func(on_entry, on_exit)(inst) # type: ignore [assignment] + inst = inst._MAKE_FN(on_entry, on_exit)(inst) # type: ignore [assignment, attr-defined] return inst -class MetaObj(MetaCommon): # noqa: D101 - def __new__( # noqa: D102 - cls, name: str, bases: Tuple[Type, ...], dct: Dict[str, Any] - ) -> "MetaCommon": - return super().__new__(cls, name, bases, dct, Jac.make_obj) - - -class MetaWalker(MetaCommon): # noqa: D101 - def __new__( # noqa: D102 - cls, name: str, bases: Tuple[Type, ...], dct: Dict[str, Any] - ) -> "MetaCommon": - return super().__new__(cls, name, bases, dct, Jac.make_walker) - - -class MetaNode(MetaCommon): # noqa: D101 - def __new__( # noqa: D102 - cls, name: str, bases: Tuple[Type, ...], dct: Dict[str, Any] - ) -> "MetaCommon": - return super().__new__(cls, name, bases, dct, Jac.make_node) - - -class MetaEdge(MetaCommon): # noqa: D101 - def __new__( # noqa: D102 - cls, name: str, bases: Tuple[Type, ...], dct: Dict[str, Any] - ) -> "MetaCommon": - return super().__new__(cls, name, bases, dct, Jac.make_edge) - - # ---------------------------------------------------------------------------- # Base classes. # ---------------------------------------------------------------------------- -class Obj(_ArchiTypeBase, metaclass=MetaObj): +class Obj(_ArchiTypeBase, metaclass=JacMeta): """Base class for all the jac object types.""" - def __init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 - """Initialize Jac architype base.""" + _MAKE_FN = Jac.make_obj -class Walker(_ArchiTypeBase, metaclass=MetaWalker): +class Walker(_ArchiTypeBase, metaclass=JacMeta): """Base class for all the jac walker types.""" - def __init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 - """Initialize Jac architype base.""" + _MAKE_FN = Jac.make_walker def spawn(self, node: "_ArchiTypeBase | Root") -> "Walker": """Spawn a new node from the walker.""" @@ -163,7 +137,7 @@ def ignore( | Node | Edge | list[Node | Root | Edge] - | List[Node | Root | Edge] + | JacList[Node | Root | Edge] )""", ) -> bool: """Ignore statement.""" @@ -176,7 +150,7 @@ def visit( | Node | Edge | list[Node | Root | Edge] - | List[Node | Root | Edge] + | JacList[Node | Root | Edge] )""", ) -> bool: """Visit statement.""" @@ -187,11 +161,10 @@ def disengage(self) -> None: Jac.disengage(self) # type: ignore [arg-type] -class Node(_ArchiTypeBase, metaclass=MetaNode): +class Node(_ArchiTypeBase, metaclass=JacMeta): """Base class for all the jac node types.""" - def __init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 - """Initialize Jac architype base.""" + _MAKE_FN = Jac.make_node def spawn(self, archi: _ArchiTypeBase) -> "Walker": """Spawn a new node from the walker.""" @@ -199,12 +172,12 @@ def spawn(self, archi: _ArchiTypeBase) -> "Walker": def connect( self, - node: "Node | Root | List[Node | Root]", + node: "Node | Root | JacList[Node | Root]", edge: "type[Edge] | Edge | None" = None, unidir: bool = False, conn_assign: tuple[tuple, tuple] | None = None, edges_only: bool = False, - ) -> "List[Node | Root| Edge]": + ) -> "JacList[Node | Root| Edge]": """Connect the current node to another node.""" # TODO: The above edge type should be reviewed, as the bellow can also take None, Edge, type[Edge]. ret = Jac.connect( @@ -215,11 +188,11 @@ def connect( ), edges_only=edges_only, ) - return List(ret) # type: ignore [arg-type] + return JacList(ret) # type: ignore [arg-type] def disconnect( self, - node: "Node | Root | List[Node | Root]", + node: "Node | Root | JacList[Node | Root]", edge: "type[Edge] | None" = None, dir: EdgeDir = EdgeDir.OUT, ) -> bool: @@ -235,10 +208,10 @@ def refs( self, edge: "type[Edge] | None" = None, cond: "Callable[[Edge], bool] | None" = None, - target: "Node | Root | List[Node|Root] | None" = None, + target: "Node | Root | JacList[Node|Root] | None" = None, dir: EdgeDir = EdgeDir.OUT, edges_only: bool = False, - ) -> "List[Node | Root | Edge]": + ) -> "JacList[Node | Root | Edge]": """Return all the connected nodes / edges.""" filter_func = ( ( @@ -256,21 +229,20 @@ def refs( filter_func=filter_func, edges_only=edges_only, ) - return List(ret) + return JacList(ret) -class Edge(_ArchiTypeBase, metaclass=MetaEdge): +class Edge(_ArchiTypeBase, metaclass=JacMeta): """Base class for all the jac edge types.""" - def __init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 - """Initialize Jac architype base.""" + _MAKE_FN = Jac.make_edge def spawn(self, archi: _ArchiTypeBase) -> "Walker": """Spawn a new node from the walker.""" return Jac.spawn_call(self, archi) # type: ignore [arg-type, return-value] -class List(Generic[T], list[T]): +class JacList(Generic[T], list[T]): """List with jac methods.""" # Reuse the methods. @@ -279,22 +251,22 @@ class List(Generic[T], list[T]): refs = Node.refs def filter( - self, ty: type[T] | None = None, fn: Callable[[T], bool] | None = None - ) -> "List[T]": + self, + ty: Type[S] | None = None, + fn: Callable[[T | S], TypeGuard[S]] | None = None, + ) -> "JacList[S]": """Filter comprehension.""" if ty and fn: - return List( - list(filter(lambda item: isinstance(item, ty) and fn(item), self)) - ) + return JacList([item for item in self if isinstance(item, ty) and fn(item)]) if ty: - return List(list(filter(lambda item: isinstance(item, ty), self))) + return JacList([item for item in self if isinstance(item, ty)]) if fn: - return List(list(filter(fn, self))) - return self + return JacList(list(filter(fn, self))) + return cast(JacList[S], self) - def assign(self, attrs: tuple[str], values: tuple[Any]) -> "List[T]": + def assign(self, attrs: tuple[str], values: tuple[Any]) -> "JacList[T]": """Assign Comprehension.""" - return List(Jac.assign_compr(self, (attrs, values))) + return JacList(Jac.assign_compr(self, (attrs, values))) # ---------------------------------------------------------------------------- diff --git a/jac/jaclang/compiler/passes/main/pyast_gen_pass.py b/jac/jaclang/compiler/passes/main/pyast_gen_pass.py index cde4949a7b..fa2d984362 100644 --- a/jac/jaclang/compiler/passes/main/pyast_gen_pass.py +++ b/jac/jaclang/compiler/passes/main/pyast_gen_pass.py @@ -2537,7 +2537,7 @@ def exit_list_val(self, node: ast.ListVal) -> None: node.gen.py_ast = [ self.sync( ast3.Call( - func=self.jaclib_obj("List"), + func=self.jaclib_obj("JacList"), args=[ self.sync( ast3.List( @@ -2659,7 +2659,7 @@ def exit_list_compr(self, node: ast.ListCompr) -> None: node.gen.py_ast = [ self.sync( ast3.Call( - func=self.jaclib_obj("List"), + func=self.jaclib_obj("JacList"), args=[ self.sync( ast3.ListComp( diff --git a/jac/jaclang/compiler/passes/main/tests/test_type_check_pass.py b/jac/jaclang/compiler/passes/main/tests/test_type_check_pass.py index a21327e975..82b63f4c41 100644 --- a/jac/jaclang/compiler/passes/main/tests/test_type_check_pass.py +++ b/jac/jaclang/compiler/passes/main/tests/test_type_check_pass.py @@ -72,23 +72,20 @@ def test_data_spatial_type_info(self) -> None: out, r"129:24 - 129:28.*SpecialVarRef - Jac.get_root\(\) \- Type\: jaclang.runtimelib.architype.Root", ) - # (thakee) Jac.node_dot ??? - # - # self.assertRegex(out, r"129:11 - 129:29.*FuncCall \- Type\: builtins\.str") - # self.assertRegex( - # out, - # r"129:15 - 129:23.*Name \- node_dot \- Type\: builtins.str, SymbolTable\: str", - # ) - - # (thakee) Root methods doesn't have type info, what to do? - # - # self.assertRegex( - # out, - # r"128:5 - 128:25.*BinaryExpr \- Type\: jaclang.runtimelib.architype.WalkerArchitype", - # ) - # self.assertRegex( - # out, - # r"48:11 - 48:28.*EdgeRefTrailer \- Type\: builtins.list\[data_spatial_types.A\]", - # ) + + self.assertRegex(out, r"129:11 - 129:29.*FuncCall \- Type\: builtins\.str") + self.assertRegex( + out, + r"129:15 - 129:23.*Name \- node_dot \- Type\: builtins.str, SymbolTable\: str", + ) + + self.assertRegex( + out, + r"128:5 - 128:25.*BinaryExpr \- Type\: jaclang.Walker", + ) + self.assertRegex( + out, + r"48:11 - 48:28.*EdgeRefTrailer \- Type\: jaclang.JacList\[data_spatial_types.A\]", + ) self.assertRegex(out, r"24:5 - 24:25.*BinaryExpr \- Type\: builtins.bool", out) diff --git a/jac/jaclang/runtimelib/architype.py b/jac/jaclang/runtimelib/architype.py index ccfe7dd27f..e19245f191 100644 --- a/jac/jaclang/runtimelib/architype.py +++ b/jac/jaclang/runtimelib/architype.py @@ -301,11 +301,16 @@ class GenericEdge(EdgeArchitype): def __init__(self) -> None: """Create Generic Edge.""" - self.spawn: Callable = lambda _: None - self.load_method_bounds() + # To avoide circular dependency between this file and jaclib/__init__.py we need to + # import the types here. + from jaclang import _ArchiTypeBase, Edge, Walker + + self.spawn: Callable[[_ArchiTypeBase], Walker] = MethodType(Edge.spawn, self) def load_method_bounds(self) -> None: """Load method bounds.""" + # Since the Shelf doesn't store the metod objects to the session dump, it doesn't + # load the methods again, so we need to load them manually. for name, func in self._method_bounds.items(): setattr(self, name, MethodType(func, self)) @@ -332,16 +337,27 @@ class Root(NodeArchitype): def __init__(self) -> None: """Create root node.""" self.__jac__ = NodeAnchor(architype=self, persistent=True, edges=[]) - # We need to define the method bounds here so that the type checker can - # assign the dynamic attributes. - self.spawn: Callable = lambda _: None - self.connect: Callable = lambda _: None - self.disconnect: Callable = lambda _: None - self.refs: Callable = lambda _: None - self.load_method_bounds() + + # To avoide circular dependency between this file and jaclib/__init__.py we need to + # import the types here. + from jaclang import _ArchiTypeBase, Edge, Node, Walker, JacList + + # We have to mark the parameter as variable number cause, they have default vaules + # and cannot be annotate the default values as such, so calling with no arguments + # for the default parameters will error in type checking. + self.spawn: Callable[[_ArchiTypeBase], Walker] = MethodType(Node.spawn, self) + self.connect: Callable[..., JacList[Node | Root | Edge]] = MethodType( + Node.connect, self + ) + self.disconnect: Callable[..., bool] = MethodType(Node.disconnect, self) + self.refs: Callable[..., JacList[Node | Root | Edge]] = MethodType( + Node.refs, self + ) def load_method_bounds(self) -> None: """Load method bounds.""" + # Since the Shelf doesn't store the metod objects to the session dump, it doesn't + # load the methods again, so we need to load them manually. for name, func in self._method_bounds.items(): setattr(self, name, MethodType(func, self)) diff --git a/jac/jaclang/tests/test_language.py b/jac/jaclang/tests/test_language.py index ce2a8c268d..b357d40e4e 100644 --- a/jac/jaclang/tests/test_language.py +++ b/jac/jaclang/tests/test_language.py @@ -119,7 +119,7 @@ def test_multi_dim_arr_slice(self) -> None: expected_outputs = [ "+-- AtomTrailer - Type: Any", - " +-- Name - arr - Type: jaclang.List[jaclang.List[builtins.int]], SymbolTable: None", + " +-- Name - arr - Type: jaclang.JacList[jaclang.JacList[builtins.int]], SymbolTable: None", " +-- IndexSlice - [IndexSlice] - Type: builtins.slice, SymbolTable: None", " +-- Token - [, ", " +-- Int - 1 - Type: Literal[1]?, SymbolTable: None",