Commit 6454a3ad authored by Tom Niget's avatar Tom Niget

Continue work on new generics, a lot of stuff works

parent f8de89ac
...@@ -55,3 +55,12 @@ x = Container(4) # ici on instancie Container avec un TypeVar donc on a `x: Cont ...@@ -55,3 +55,12 @@ x = Container(4) # ici on instancie Container avec un TypeVar donc on a `x: Cont
# et on lit le corps de __init__ pour faire le reste de l'unification # et on lit le corps de __init__ pour faire le reste de l'unification
# self.x est de type A, via l'assignation on unifie A=C=int # self.x est de type A, via l'assignation on unifie A=C=int
# donc x: Container[int] # donc x: Container[int]
---------------------------------------------------------------------------
fonction : type concret qui a un type caché qui correspond à ce qui sera générique en gros
pareil pour les classes
donc on peut passer des types génériques puisqu'ils sont wrappés par un type concret
...@@ -12,6 +12,7 @@ dependencies = [ ...@@ -12,6 +12,7 @@ dependencies = [
"pygments ~= 2.15.1", "pygments ~= 2.15.1",
"colorful ~= 0.5.5", "colorful ~= 0.5.5",
"pybind11 ~= 2.11.1", "pybind11 ~= 2.11.1",
"python-dotenv ~= 1.0.1",
] ]
[project.optional-dependencies] [project.optional-dependencies]
......
from typing import Self, Generic, Protocol, Optional from typing import Self, Generic, Protocol, Optional
assert 5
class object: class object:
def __eq__(self, other: Self) -> bool: ... def __eq__(self, other: Self) -> bool: ...
def __ne__(self, other: Self) -> bool: ... def __ne__(self, other: Self) -> bool: ...
......
# coding: utf-8 # coding: utf-8
from dataclasses import dataclass # from dataclasses import dataclass
#
#
#
@dataclass # @dataclass
class Thing[T]: # class Thing[T]:
x: T # x: T
def f[T](x: T): # def f[T](x: T):
pass # pass
#
#
#
#
if __name__ == "__main__": # if __name__ == "__main__":
a = Thing[int](1) # a = Thing[int](1)
b = Thing[str]("abc") # b = Thing[str]("abc")
print(a) # print(a)
print(b) # print(b)
...@@ -15,12 +15,11 @@ from transpiler.phases.desugar_op import DesugarOp ...@@ -15,12 +15,11 @@ from transpiler.phases.desugar_op import DesugarOp
colorama.init() colorama.init()
from transpiler.phases.emit_cpp.consts import MAPPINGS
from transpiler.exceptions import CompileError from transpiler.exceptions import CompileError
from transpiler.phases.desugar_with import DesugarWith from transpiler.phases.desugar_with import DesugarWith
from transpiler.phases.emit_cpp.file import FileVisitor #from transpiler.phases.emit_cpp.file import FileVisitor
from transpiler.phases.if_main import IfMainVisitor from transpiler.phases.if_main import IfMainVisitor
from transpiler.phases.typing.block import ScoperBlockVisitor #from transpiler.phases.typing.block import ScoperBlockVisitor
from transpiler.phases.typing.scope import Scope from transpiler.phases.typing.scope import Scope
from transpiler.utils import highlight from transpiler.utils import highlight
...@@ -179,12 +178,12 @@ else: ...@@ -179,12 +178,12 @@ else:
def transpile(source, name: str, path=None): def transpile(source, name: str, path=None):
__TB__ = f"transpiling module {cf.white(name)}" __TB__ = f"transpiling module {cf.white(name)}"
res = ast.parse(source, type_comments=True) res = ast.parse(source, type_comments=True)
exit()
IfMainVisitor().visit(res) IfMainVisitor().visit(res)
res = DesugarWith().visit(res) res = DesugarWith().visit(res)
res = DesugarCompare().visit(res) res = DesugarCompare().visit(res)
res = DesugarOp().visit(res) res = DesugarOp().visit(res)
ScoperBlockVisitor().visit(res) #ScoperBlockVisitor().visit(res)
# print(res.scope) # print(res.scope)
......
...@@ -4,7 +4,7 @@ from logging import debug ...@@ -4,7 +4,7 @@ from logging import debug
from transpiler.phases.typing.scope import VarKind, VarDecl, ScopeKind, Scope from transpiler.phases.typing.scope import VarKind, VarDecl, ScopeKind, Scope
from transpiler.phases.typing.stdlib import PRELUDE, StdlibVisitor from transpiler.phases.typing.stdlib import PRELUDE, StdlibVisitor
from transpiler.phases.typing.types import TY_TYPE, TY_INT, TY_STR, TY_BOOL, TY_COMPLEX, TY_NONE, ResolvedConcreteType, \ from transpiler.phases.typing.types import TY_TYPE, TY_INT, TY_STR, TY_BOOL, TY_COMPLEX, TY_NONE, ResolvedConcreteType, \
MemberDef MemberDef, TY_FLOAT, TY_BUILTIN_FEATURE, TY_TUPLE, TY_DICT, TY_SET, TY_LIST, TY_BYTES, TY_OBJECT, TY_CPP_TYPE
# PRELUDE.vars.update({ # PRELUDE.vars.update({
# "int": VarDecl(VarKind.LOCAL, TypeType(TY_INT)), # "int": VarDecl(VarKind.LOCAL, TypeType(TY_INT)),
...@@ -31,6 +31,24 @@ from transpiler.phases.typing.types import TY_TYPE, TY_INT, TY_STR, TY_BOOL, TY_ ...@@ -31,6 +31,24 @@ from transpiler.phases.typing.types import TY_TYPE, TY_INT, TY_STR, TY_BOOL, TY_
# "Optional": VarDecl(VarKind.LOCAL, TypeType(lambda x: UnionType(x, TY_NONE))), # "Optional": VarDecl(VarKind.LOCAL, TypeType(lambda x: UnionType(x, TY_NONE))),
# }) # })
prelude_vars = {
"object": TY_OBJECT,
"bool": TY_BOOL,
"int": TY_INT,
"float": TY_FLOAT,
"str": TY_STR,
"bytes": TY_BYTES,
"complex": TY_COMPLEX,
"list": TY_LIST,
"set": TY_SET,
"dict": TY_DICT,
"tuple": TY_TUPLE,
"BuiltinFeature": TY_BUILTIN_FEATURE,
"CppType": TY_CPP_TYPE
}
PRELUDE.vars.update({name: VarDecl(VarKind.LOCAL, ty.type_type()) for name, ty in prelude_vars.items()})
typon_std = Path(__file__).parent.parent.parent.parent / "stdlib" typon_std = Path(__file__).parent.parent.parent.parent / "stdlib"
def make_module(name: str, scope: Scope) -> ResolvedConcreteType: def make_module(name: str, scope: Scope) -> ResolvedConcreteType:
......
...@@ -4,7 +4,8 @@ from dataclasses import dataclass, field ...@@ -4,7 +4,8 @@ from dataclasses import dataclass, field
from typing import Optional, List from typing import Optional, List
from transpiler.phases.typing.scope import Scope from transpiler.phases.typing.scope import Scope
from transpiler.phases.typing.types import BaseType, TY_NONE, TypeVariable, TY_TYPE, ResolvedConcreteType, TypeListType from transpiler.phases.typing.types import BaseType, TY_NONE, TypeVariable, TY_TYPE, ResolvedConcreteType, TypeListType, \
TY_BUILTIN_FEATURE, make_builtin_feature, TY_CPP_TYPE, make_cpp_type, GenericType, TY_UNION
from transpiler.phases.utils import NodeVisitorSeq from transpiler.phases.utils import NodeVisitorSeq
...@@ -33,11 +34,18 @@ class TypeAnnotationVisitor(NodeVisitorSeq): ...@@ -33,11 +34,18 @@ class TypeAnnotationVisitor(NodeVisitorSeq):
raise NotImplementedError raise NotImplementedError
def visit_Subscript(self, node: ast.Subscript) -> BaseType: def visit_Subscript(self, node: ast.Subscript) -> BaseType:
# ty_op = self.visit(node.value) ty_op = self.visit(node.value)
# args = list(node.slice.elts) if type(node.slice) == ast.Tuple else [node.slice] args = list(node.slice.elts) if type(node.slice) == ast.Tuple else [node.slice]
# args = [self.visit(arg) for arg in args] args = [self.visit(arg) for arg in args]
if ty_op is TY_BUILTIN_FEATURE:
assert len(args) == 1
return make_builtin_feature(args[0])
elif ty_op is TY_CPP_TYPE:
assert len(args) == 1
return make_cpp_type(args[0])
# return ty_op(*args) # return ty_op(*args)
raise NotImplementedError() assert isinstance(ty_op, GenericType)
return ty_op.instantiate(args)
# return TypeOperator([self.visit(node.value)], self.visit(node.slice.value)) # return TypeOperator([self.visit(node.value)], self.visit(node.slice.value))
def visit_List(self, node: ast.List) -> BaseType: def visit_List(self, node: ast.List) -> BaseType:
...@@ -51,6 +59,6 @@ class TypeAnnotationVisitor(NodeVisitorSeq): ...@@ -51,6 +59,6 @@ class TypeAnnotationVisitor(NodeVisitorSeq):
# return res.type_object # return res.type_object
def visit_BinOp(self, node: ast.BinOp) -> BaseType: def visit_BinOp(self, node: ast.BinOp) -> BaseType:
# if isinstance(node.op, ast.BitOr): if isinstance(node.op, ast.BitOr):
# return UnionType(self.visit(node.left), self.visit(node.right)) return TY_UNION.instantiate([self.visit(node.left), self.visit(node.right)])
raise NotImplementedError(node.op) raise NotImplementedError(node.op)
...@@ -4,7 +4,7 @@ from dataclasses import dataclass ...@@ -4,7 +4,7 @@ from dataclasses import dataclass
from transpiler.utils import highlight from transpiler.utils import highlight
from transpiler.exceptions import CompileError from transpiler.exceptions import CompileError
from transpiler.phases.typing.types import TypeVariable, BaseType, TypeOperator from transpiler.phases.typing.types import TypeVariable, BaseType
@dataclass @dataclass
...@@ -85,8 +85,8 @@ class TypeMismatchError(CompileError): ...@@ -85,8 +85,8 @@ class TypeMismatchError(CompileError):
@dataclass @dataclass
class ArgumentCountMismatchError(CompileError): class ArgumentCountMismatchError(CompileError):
func: TypeOperator func: "TypeOperator"
arguments: TypeOperator arguments: "TypeOperator"
def __str__(self) -> str: def __str__(self) -> str:
fcount = str(len(self.func.args)) fcount = str(len(self.func.args))
...@@ -309,7 +309,7 @@ class InconsistentMroError(CompileError): ...@@ -309,7 +309,7 @@ class InconsistentMroError(CompileError):
bases: list[BaseType] bases: list[BaseType]
def __str__(self) -> str: def __str__(self) -> str:
return f"Cannot create a cnossitent method resolution order (MRO) for bases {'\n'.join(map(highlight, self.bases))}" return f"Cannot create a consistent method resolution order (MRO) for bases {'\n'.join(map(highlight, self.bases))}"
def detail(self, last_node: ast.AST = None) -> str: def detail(self, last_node: ast.AST = None) -> str:
return f""" return f"""
......
...@@ -5,7 +5,9 @@ from typing import List ...@@ -5,7 +5,9 @@ from typing import List
from transpiler.phases.typing import ScopeKind, VarDecl, VarKind from transpiler.phases.typing import ScopeKind, VarDecl, VarKind
from transpiler.phases.typing.common import ScoperVisitor, get_iter, get_next, is_builtin from transpiler.phases.typing.common import ScoperVisitor, get_iter, get_next, is_builtin
from transpiler.phases.typing.types import BaseType from transpiler.phases.typing.types import BaseType, TY_STR, TY_BOOL, TY_INT, TY_COMPLEX, TY_FLOAT, TY_NONE, \
ClassTypeType, ResolvedConcreteType, GenericType, CallableInstanceType, TY_LIST, TY_SET, TY_DICT, RuntimeValue, \
TypeVariable, TY_LAMBDA, TypeListType
from transpiler.utils import linenodata from transpiler.utils import linenodata
DUNDER = { DUNDER = {
...@@ -91,8 +93,8 @@ class ScoperExprVisitor(ScoperVisitor): ...@@ -91,8 +93,8 @@ class ScoperExprVisitor(ScoperVisitor):
from transpiler.phases.typing.exceptions import UnknownNameError from transpiler.phases.typing.exceptions import UnknownNameError
raise UnknownNameError(node.id) raise UnknownNameError(node.id)
ty = obj.type.resolve() ty = obj.type.resolve()
if isinstance(ty, TypeType) and isinstance(ty.type_object, TypeVariable): # if isinstance(ty, TypeType) and isinstance(ty.type_object, TypeVariable):
raise NameError(f"Use of type variable") # todo: when does this happen exactly? # raise NameError(f"Use of type variable") # todo: when does this happen exactly?
if getattr(ty, "is_python_func", False): if getattr(ty, "is_python_func", False):
ty.python_func_used = True ty.python_func_used = True
return ty return ty
...@@ -104,46 +106,48 @@ class ScoperExprVisitor(ScoperVisitor): ...@@ -104,46 +106,48 @@ class ScoperExprVisitor(ScoperVisitor):
def visit_Call(self, node: ast.Call) -> BaseType: def visit_Call(self, node: ast.Call) -> BaseType:
ftype = self.visit(node.func) ftype = self.visit(node.func)
if is_builtin(ftype, "TypeVar"):
return TypeType(TypeVariable(*[ast.literal_eval(arg) for arg in node.args]))
if ftype.typevars:
ftype = ftype.gen_sub(None, {v.name: TypeVariable(v.name) for v in ftype.typevars})
from transpiler.exceptions import CompileError from transpiler.exceptions import CompileError
rtype = self.visit_function_call(ftype, [self.visit(arg) for arg in node.args]) rtype = self.visit_function_call(ftype, [self.visit(arg) for arg in node.args])
actual = rtype actual = rtype
node.is_await = False node.is_await = False
if isinstance(actual, Promise) and actual.kind != PromiseKind.GENERATOR: # if isinstance(actual, Promise) and actual.kind != PromiseKind.GENERATOR:
node.is_await = True # node.is_await = True
actual = actual.return_type.resolve() # actual = actual.return_type.resolve()
#
if self.scope.function and isinstance(actual, Promise) and actual.kind == PromiseKind.FORKED \ # if self.scope.function and isinstance(actual, Promise) and actual.kind == PromiseKind.FORKED \
and isinstance(fty := self.scope.function.obj_type.return_type, Promise): # and isinstance(fty := self.scope.function.obj_type.return_type, Promise):
fty.kind = PromiseKind.JOIN # fty.kind = PromiseKind.JOIN
return actual return actual
def visit_function_call(self, ftype: BaseType, arguments: List[BaseType]): def visit_function_call(self, ftype: ResolvedConcreteType, arguments: List[BaseType]):
if isinstance(ftype, TypeType):# and isinstance(ftype.type_object, UserType): if isinstance(ftype, GenericType):
init: FunctionType = self.visit_getattr(ftype, "__init__").remove_self() ftype = ftype.instantiate_default()
init.return_type = ftype.type_object assert isinstance(ftype, CallableInstanceType)
return self.visit_function_call(init, arguments) for a, b in zip(ftype.parameters, arguments):
if isinstance(ftype, FunctionType): a.try_assign(b)
ret = ftype.return_type return ftype.return_type
elif isinstance(ftype, TypeVariable): # if isinstance(ftype, TypeType):# and isinstance(ftype.type_object, UserType):
ret = TypeVariable() # init: FunctionType = self.visit_getattr(ftype, "__init__").remove_self()
else: # init.return_type = ftype.type_object
from transpiler.phases.typing.exceptions import NotCallableError # return self.visit_function_call(init, arguments)
raise NotCallableError(ftype) # if isinstance(ftype, FunctionType):
#is_generic = any(isinstance(arg, TypeVariable) for arg in ftype.to_list()) # ret = ftype.return_type
equivalent = FunctionType(arguments, ret) # elif isinstance(ftype, TypeVariable):
equivalent.is_intermediary = True # ret = TypeVariable()
ftype.unify(equivalent) # else:
return equivalent.return_type # from transpiler.phases.typing.exceptions import NotCallableError
# raise NotCallableError(ftype)
# #is_generic = any(isinstance(arg, TypeVariable) for arg in ftype.to_list())
# equivalent = FunctionType(arguments, ret)
# equivalent.is_intermediary = True
# ftype.unify(equivalent)
# return equivalent.return_type
def visit_Lambda(self, node: ast.Lambda) -> BaseType: def visit_Lambda(self, node: ast.Lambda) -> BaseType:
argtypes = [TypeVariable() for _ in node.args.args] argtypes = [TypeVariable() for _ in node.args.args]
rtype = TypeVariable() rtype = TypeVariable()
ftype = FunctionType(argtypes, rtype) ftype = TY_LAMBDA.instantiate([TypeListType(argtypes), rtype])
scope = self.scope.child(ScopeKind.FUNCTION) scope = self.scope.child(ScopeKind.FUNCTION)
scope.obj_type = ftype scope.obj_type = ftype
scope.function = scope scope.function = scope
...@@ -173,21 +177,27 @@ class ScoperExprVisitor(ScoperVisitor): ...@@ -173,21 +177,27 @@ class ScoperExprVisitor(ScoperVisitor):
return self.visit_getattr(ltype, node.attr) return self.visit_getattr(ltype, node.attr)
def visit_getattr(self, ltype: BaseType, name: str) -> BaseType: def visit_getattr(self, ltype: BaseType, name: str) -> BaseType:
bound = True bound = True
if isinstance(ltype, TypeType): if isinstance(ltype, ClassTypeType):
# if mdecl := ltype.static_members.get(name): # if mdecl := ltype.static_members.get(name):
# attr = mdecl.type # attr = mdecl.type
# if getattr(attr, "is_python_func", False): # if getattr(attr, "is_python_func", False):
# attr.python_func_used = True # attr.python_func_used = True
# return attr # return attr
ltype = ltype.type_object ltype = ltype.inner_type
bound = False bound = False
if isinstance(ltype, abc.ABCMeta):
ctor = ltype.__init__ if isinstance(ltype, GenericType):
args = list(inspect.signature(ctor).parameters.values())[1:] ltype = ltype.instantiate_default()
if not all(arg.annotation == BaseType for arg in args):
raise NotImplementedError("I don't know how to handle this type") assert isinstance(ltype, ResolvedConcreteType)
ltype = ltype(*(TypeVariable() for _ in args)) # if isinstance(ltype, abc.ABCMeta):
# ctor = ltype.__init__
# args = list(inspect.signature(ctor).parameters.values())[1:]
# if not all(arg.annotation == BaseType for arg in args):
# raise NotImplementedError("I don't know how to handle this type")
# ltype = ltype(*(TypeVariable() for _ in args))
# if mdecl := ltype.members.get(name): # if mdecl := ltype.members.get(name):
# attr = mdecl.type # attr = mdecl.type
# if getattr(attr, "is_python_func", False): # if getattr(attr, "is_python_func", False):
...@@ -203,15 +213,14 @@ class ScoperExprVisitor(ScoperVisitor): ...@@ -203,15 +213,14 @@ class ScoperExprVisitor(ScoperVisitor):
ty = field.type.resolve() ty = field.type.resolve()
if getattr(ty, "is_python_func", False): if getattr(ty, "is_python_func", False):
ty.python_func_used = True ty.python_func_used = True
if isinstance(ty, FunctionType): if isinstance(ty, CallableInstanceType):
ty = ty.gen_sub(ltype, {})
if bound and field.in_class_def and type(field.val) != RuntimeValue: if bound and field.in_class_def and type(field.val) != RuntimeValue:
return ty.remove_self() return ty.remove_self()
return ty return ty
from transpiler.phases.typing.exceptions import MissingAttributeError from transpiler.phases.typing.exceptions import MissingAttributeError
parents = ltype.iter_hierarchy_recursive() parents = iter(ltype.get_mro())
next(parents) next(parents)
for p in parents: for p in parents:
try: try:
...@@ -224,7 +233,7 @@ class ScoperExprVisitor(ScoperVisitor): ...@@ -224,7 +233,7 @@ class ScoperExprVisitor(ScoperVisitor):
def visit_List(self, node: ast.List) -> BaseType: def visit_List(self, node: ast.List) -> BaseType:
if not node.elts: if not node.elts:
return PyList(TypeVariable()) return TY_LIST.instantiate_default()
elems = [self.visit(e) for e in node.elts] elems = [self.visit(e) for e in node.elts]
first, *rest = elems first, *rest = elems
for e in rest: for e in rest:
...@@ -232,33 +241,31 @@ class ScoperExprVisitor(ScoperVisitor): ...@@ -232,33 +241,31 @@ class ScoperExprVisitor(ScoperVisitor):
first.unify(e) first.unify(e)
except: except:
raise NotImplementedError(f"List with different types not handled yet: {', '.join(map(str, elems))}") raise NotImplementedError(f"List with different types not handled yet: {', '.join(map(str, elems))}")
return PyList(elems[0]) return TY_LIST.instantiate([elems[0]])
def visit_Set(self, node: ast.Set) -> BaseType: def visit_Set(self, node: ast.Set) -> BaseType:
if not node.elts: if not node.elts:
return PySet(TypeVariable()) return TY_SET.instantiate_default()
elems = [self.visit(e) for e in node.elts] elems = [self.visit(e) for e in node.elts]
if len(set(elems)) != 1: if len(set(elems)) != 1:
raise NotImplementedError("Set with different types not handled yet") raise NotImplementedError("Set with different types not handled yet")
return PySet(elems[0]) return TY_SET.instantiate([elems[0]])
def visit_Dict(self, node: ast.Dict) -> BaseType: def visit_Dict(self, node: ast.Dict) -> BaseType:
if not node.keys: if not node.keys:
return PyDict(TypeVariable(), TypeVariable()) return TY_DICT.instantiate_default()
keys = [self.visit(e) for e in node.keys] keys = [self.visit(e) for e in node.keys]
values = [self.visit(e) for e in node.values] values = [self.visit(e) for e in node.values]
if len(set(keys)) != 1 or len(set(values)) != 1: if len(set(keys)) != 1 or len(set(values)) != 1:
raise NotImplementedError(f"Dict with different types not handled yet in `{ast.unparse(node)}`") raise NotImplementedError(f"Dict with different types not handled yet in `{ast.unparse(node)}`")
return PyDict(keys[0], values[0]) return TY_DICT.instantiate([keys[0], values[0]])
def visit_Subscript(self, node: ast.Subscript) -> BaseType: def visit_Subscript(self, node: ast.Subscript) -> BaseType:
left = self.visit(node.value) left = self.visit(node.value)
if isinstance(left, ClassTypeType):
return self.anno().visit(node)
args = node.slice if type(node.slice) == tuple else [node.slice] args = node.slice if type(node.slice) == tuple else [node.slice]
args = [self.visit(e) for e in args] args = [self.visit(e) for e in args]
if isinstance(left, TypeType) and isinstance(left.type_object, abc.ABCMeta):
# generic
return TypeType(left.type_object(*[arg.type_object if isinstance(arg, TypeType) else arg for arg in args]))
pass
return self.make_dunder([left, *args], "getitem") return self.make_dunder([left, *args], "getitem")
def visit_UnaryOp(self, node: ast.UnaryOp) -> BaseType: def visit_UnaryOp(self, node: ast.UnaryOp) -> BaseType:
...@@ -277,7 +284,7 @@ class ScoperExprVisitor(ScoperVisitor): ...@@ -277,7 +284,7 @@ class ScoperExprVisitor(ScoperVisitor):
def make_dunder(self, args: List[BaseType], name: str) -> BaseType: def make_dunder(self, args: List[BaseType], name: str) -> BaseType:
return self.visit_function_call( return self.visit_function_call(
self.visit_getattr(TypeType(args[0]), f"__{name}__"), self.visit_getattr(args[0], f"__{name}__"),
args args
) )
......
import ast import ast
from dataclasses import field, dataclass from dataclasses import field, dataclass
from enum import Enum from enum import Enum
from typing import Optional, Dict, List, Any from typing import Optional, Dict, List, Any, Self
from transpiler.phases.typing.types import BaseType, RuntimeValue from transpiler.phases.typing.types import BaseType, RuntimeValue
...@@ -67,7 +67,7 @@ class Scope: ...@@ -67,7 +67,7 @@ class Scope:
return self.parent.is_in_loop() return self.parent.is_in_loop()
return None return None
def child(self, kind: ScopeKind): def child(self, kind: ScopeKind) -> Self:
res = Scope(self, kind, self.function, self.global_scope) res = Scope(self, kind, self.function, self.global_scope)
if kind == ScopeKind.GLOBAL: if kind == ScopeKind.GLOBAL:
res.global_scope = res res.global_scope = res
......
...@@ -4,13 +4,15 @@ from abc import ABCMeta ...@@ -4,13 +4,15 @@ from abc import ABCMeta
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Optional, List, Dict, Callable from typing import Optional, List, Dict, Callable
from logging import debug from logging import debug
from transpiler.utils import highlight
from transpiler.phases.typing.annotations import TypeAnnotationVisitor from transpiler.phases.typing.annotations import TypeAnnotationVisitor
from transpiler.phases.typing.common import PRELUDE, is_builtin from transpiler.phases.typing.common import PRELUDE, is_builtin
from transpiler.phases.typing.expr import ScoperExprVisitor from transpiler.phases.typing.expr import ScoperExprVisitor
from transpiler.phases.typing.scope import Scope, VarDecl, VarKind, ScopeKind from transpiler.phases.typing.scope import Scope, VarDecl, VarKind, ScopeKind
from transpiler.phases.typing.types import BaseType, BuiltinGenericType, BuiltinType, create_builtin_generic_type, \ from transpiler.phases.typing.types import BaseType, BuiltinGenericType, BuiltinType, create_builtin_generic_type, \
create_builtin_type, ConcreteType, GenericInstanceType, TypeListType, TypeTupleType, GenericParameter, \ create_builtin_type, ConcreteType, GenericInstanceType, TypeListType, TypeTupleType, GenericParameter, \
GenericParameterKind, TypeVariable GenericParameterKind, TypeVariable, ResolvedConcreteType, MemberDef, ClassTypeType, CallableInstanceType
from transpiler.phases.utils import NodeVisitorSeq from transpiler.phases.utils import NodeVisitorSeq
def visit_generic_item( def visit_generic_item(
...@@ -18,8 +20,9 @@ def visit_generic_item( ...@@ -18,8 +20,9 @@ def visit_generic_item(
node, node,
output_type: BuiltinGenericType, output_type: BuiltinGenericType,
scope: Scope, scope: Scope,
instance_type = None): instance_type = None,
if node.type_params: force_generic = False):
if force_generic or node.type_params:
output_type.parameters = [] output_type.parameters = []
for param in node.type_params: for param in node.type_params:
match param: match param:
...@@ -47,19 +50,21 @@ def visit_generic_item( ...@@ -47,19 +50,21 @@ def visit_generic_item(
op_val = TypeVariable() op_val = TypeVariable()
match param: match param:
case ast.TypeVar(name, bound): case ast.TypeVar(name, bound):
new_scope.declare_local(name, op_val) new_scope.declare_local(name, op_val.type_type())
if bound is not None: if bound is not None:
constraints.append((op_val, anno.visit(bound))) constraints.append((op_val, anno.visit(bound)))
case ast.ParamSpec(name): case ast.ParamSpec(name):
assert isinstance(op_val, TypeListType) assert isinstance(op_val, TypeListType)
new_scope.declare_local(name, op_val) new_scope.declare_local(name, op_val.type_type())
case ast.TypeVarTuple(name): case ast.TypeVarTuple(name):
new_scope.declare_local(name, TypeTupleType(list(args_iter))) new_scope.declare_local(name, TypeTupleType(list(args_iter)).type_type())
for a, b in constraints: # for a, b in constraints:
raise NotImplementedError() # raise NotImplementedError()
# todo
new_output_type = instance_type() new_output_type = instance_type()
visit_nongeneric(new_scope, new_output_type) visit_nongeneric(new_scope, new_output_type)
return new_output_type return new_output_type
output_type.constraints_ = []
output_type.instantiate_ = instantiate output_type.instantiate_ = instantiate
else: else:
visit_nongeneric(scope, output_type) visit_nongeneric(scope, output_type)
...@@ -68,7 +73,7 @@ def visit_generic_item( ...@@ -68,7 +73,7 @@ def visit_generic_item(
@dataclass @dataclass
class StdlibVisitor(NodeVisitorSeq): class StdlibVisitor(NodeVisitorSeq):
scope: Scope = field(default_factory=lambda: PRELUDE) scope: Scope = field(default_factory=lambda: PRELUDE)
cur_class: Optional[BaseType] = None cur_class: Optional[ResolvedConcreteType] = None
typevars: Dict[str, BaseType] = field(default_factory=dict) typevars: Dict[str, BaseType] = field(default_factory=dict)
def expr(self) -> ScoperExprVisitor: def expr(self) -> ScoperExprVisitor:
...@@ -84,8 +89,8 @@ class StdlibVisitor(NodeVisitorSeq): ...@@ -84,8 +89,8 @@ class StdlibVisitor(NodeVisitorSeq):
def visit_AnnAssign(self, node: ast.AnnAssign): def visit_AnnAssign(self, node: ast.AnnAssign):
ty = self.anno().visit(node.annotation) ty = self.anno().visit(node.annotation)
if self.cur_class: if self.cur_class:
assert isinstance(self.cur_class, TypeType) assert isinstance(self.cur_class, ResolvedConcreteType)
self.cur_class.type_object.fields[node.target.id] = MemberDef(ty.gen_sub(self.cur_class.type_object, self.typevars)) self.cur_class.fields[node.target.id] = MemberDef(ty)
self.scope.vars[node.target.id] = VarDecl(VarKind.LOCAL, ty) self.scope.vars[node.target.id] = VarDecl(VarKind.LOCAL, ty)
def visit_ImportFrom(self, node: ast.ImportFrom): def visit_ImportFrom(self, node: ast.ImportFrom):
...@@ -96,14 +101,16 @@ class StdlibVisitor(NodeVisitorSeq): ...@@ -96,14 +101,16 @@ class StdlibVisitor(NodeVisitorSeq):
def visit_ClassDef(self, node: ast.ClassDef): def visit_ClassDef(self, node: ast.ClassDef):
if existing := self.scope.get(node.name): if existing := self.scope.get(node.name):
NewType = existing.type assert isinstance(existing.type, ClassTypeType)
NewType = existing.type.inner_type
else: else:
base_class = create_builtin_generic_type if node.type_params else create_builtin_type base_class = create_builtin_generic_type if node.type_params else create_builtin_type
NewType = base_class(node.name) NewType = base_class(node.name)
self.scope.vars[node.name] = VarDecl(VarKind.LOCAL, NewType.type_type()) self.scope.vars[node.name] = VarDecl(VarKind.LOCAL, NewType.type_type())
def visit_nongeneric(scope, output: ConcreteType): def visit_nongeneric(scope: Scope, output: ConcreteType):
cl_scope = scope.child(ScopeKind.CLASS) cl_scope = scope.child(ScopeKind.CLASS)
cl_scope.declare_local("Self", output.type_type())
visitor = StdlibVisitor(cl_scope, output) visitor = StdlibVisitor(cl_scope, output)
for stmt in node.body: for stmt in node.body:
visitor.visit(stmt) visitor.visit(stmt)
...@@ -114,17 +121,74 @@ class StdlibVisitor(NodeVisitorSeq): ...@@ -114,17 +121,74 @@ class StdlibVisitor(NodeVisitorSeq):
pass pass
def visit_FunctionDef(self, node: ast.FunctionDef): def visit_FunctionDef(self, node: ast.FunctionDef):
def visit_nongeneric(scope, output: ConcreteType): def visit_nongeneric(scope, output: CallableInstanceType):
cl_scope = scope.child(ScopeKind.CLASS) scope = scope.child(ScopeKind.FUNCTION)
visitor = StdlibVisitor(cl_scope, output) arg_visitor = TypeAnnotationVisitor(scope)
for stmt in node.body: output.parameters = [arg_visitor.visit(arg.annotation) for arg in node.args.args]
visitor.visit(stmt) output.return_type = arg_visitor.visit(node.returns)
@dataclass(eq=False, init=False)
class InstanceType(CallableInstanceType):
def __init__(self):
super().__init__([], None)
def __str__(self):
return f"{node.name}({", ".join(map(str, self.parameters))}) -> {self.return_type}"
''' '''
class arguments(__ast.AST): class arguments(__ast.AST):
""" arguments(arg* posonlyargs, arg* args, arg? vararg, arg* kwonlyargs, expr* kw_defaults, arg? kwarg, expr* defaults) """''' """ arguments(arg* posonlyargs, arg* args, arg? vararg, arg* kwonlyargs, expr* kw_defaults, arg? kwarg, expr* defaults) """'''
visit_generic_item(visit_nongeneric, node, NewType, self.scope) args = node.args
assert args.posonlyargs == []
#assert args.vararg is None TODO
assert args.kwonlyargs == []
assert args.kw_defaults == []
assert args.kwarg is None
#assert args.defaults == [] TODO
for i, arg in enumerate(args.args):
arg: ast.arg
""" arg(identifier arg, expr? annotation, string? type_comment) """
if arg.annotation is None:
if i == 0 and self.cur_class is not None:
arg_name = "Self"
else:
arg_name = f"AutoVar${hash(arg.arg)}"
node.type_params.append(ast.TypeVar(arg_name, None)) # todo: bounds
arg.annotation = ast.Name(arg_name, ast.Load())
else:
if isinstance(arg.annotation, ast.Name) and (
arg.annotation.id == "Self" or
any(k.name == arg.annotation.id for k in node.type_params)
):
# annotation is type variable so we keep it
pass
else:
arg_name = f"AutoBoundedVar${hash(arg.arg)}"
node.type_params.append(ast.TypeVar(arg_name, arg.annotation))
arg.annotation = ast.Name(arg_name, ast.Load())
# if self.cur_class is not None:
# node.type_params.append(ast.TypeVar("Self", None))
if True or node.type_params:
class FuncType(BuiltinGenericType):
def name(self):
return f"FuncTypeGen${node.name}"
else:
class FuncType(InstanceType):
pass
base_class = FuncType
NewType = base_class()
FuncType.__name__ = NewType.name()
self.scope.vars[node.name] = VarDecl(VarKind.LOCAL, NewType)
if self.cur_class is not None:
self.cur_class.fields[node.name] = MemberDef(NewType, node)
visit_generic_item(visit_nongeneric, node, NewType, self.scope, InstanceType, True)
# tc = node.type_comment # todo : lire les commetnaries de type pour les fonctions génériques sinon trouver autre chose # tc = node.type_comment # todo : lire les commetnaries de type pour les fonctions génériques sinon trouver autre chose
...@@ -155,18 +219,10 @@ class StdlibVisitor(NodeVisitorSeq): ...@@ -155,18 +219,10 @@ class StdlibVisitor(NodeVisitorSeq):
else: else:
raise AssertionError(f"Assertion should fail, got {res} for {ast.unparse(oper)}") raise AssertionError(f"Assertion should fail, got {res} for {ast.unparse(oper)}")
else: else:
debug(f"Type of {ast.unparse(node.test)} := {self.expr().visit(node.test)}") debug(f"Type of {highlight(ast.unparse(node.test))} := {highlight(self.expr().visit(node.test))}")
def visit_Call(self, node: ast.Call) -> BaseType:
ty_op = self.visit(node.func)
if is_builtin(ty_op, "TypeVar"):
return TypeType(TypeVariable(*[ast.literal_eval(arg) for arg in node.args]))
if isinstance(ty_op, TypeType):
return TypeType(ty_op.type_object(*[ast.literal_eval(arg) for arg in node.args]))
raise NotImplementedError(ast.unparse(node))
def anno(self) -> "TypeAnnotationVisitor": def anno(self) -> "TypeAnnotationVisitor":
return TypeAnnotationVisitor(self.scope, self.cur_class) return TypeAnnotationVisitor(self.scope)
def visit_str(self, node: str) -> BaseType: def visit_str(self, node: str) -> BaseType:
if existing := self.scope.get(node): if existing := self.scope.get(node):
...@@ -175,6 +231,4 @@ class StdlibVisitor(NodeVisitorSeq): ...@@ -175,6 +231,4 @@ class StdlibVisitor(NodeVisitorSeq):
raise UnknownNameError(node) raise UnknownNameError(node)
def visit_Name(self, node: ast.Name) -> BaseType: def visit_Name(self, node: ast.Name) -> BaseType:
if node.id == "TypeVar":
return BuiltinFeature("TypeVar")
return self.visit_str(node.id) return self.visit_str(node.id)
\ No newline at end of file
...@@ -4,6 +4,8 @@ from abc import ABC, abstractmethod ...@@ -4,6 +4,8 @@ from abc import ABC, abstractmethod
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Dict, Optional, Callable from typing import Dict, Optional, Callable
from transpiler.utils import highlight
def get_default_parents(): def get_default_parents():
if obj := globals().get("TY_OBJECT"): if obj := globals().get("TY_OBJECT"):
...@@ -21,6 +23,15 @@ class MemberDef: ...@@ -21,6 +23,15 @@ class MemberDef:
val: typing.Any = RuntimeValue() val: typing.Any = RuntimeValue()
in_class_def: bool = True in_class_def: bool = True
@dataclass
class UnifyMode:
search_hierarchy: bool = True
match_protocol: bool = True
UnifyMode.NORMAL = UnifyMode()
UnifyMode.EXACT = UnifyMode(False, False)
@dataclass(eq=False) @dataclass(eq=False)
class BaseType(ABC): class BaseType(ABC):
...@@ -30,6 +41,39 @@ class BaseType(ABC): ...@@ -30,6 +41,39 @@ class BaseType(ABC):
def type_type(self) -> "ClassTypeType": def type_type(self) -> "ClassTypeType":
return TY_TYPE.instantiate([self]) return TY_TYPE.instantiate([self])
@abstractmethod
def name(self) -> str:
...
def __str__(self):
return self.name()
@abstractmethod
def unify_internal(self, other: "BaseType", mode: UnifyMode):
pass
def unify(self, other: "BaseType", mode = UnifyMode.NORMAL):
a, b = self.resolve(), other.resolve()
__TB__ = f"unifying {highlight(a)} and {highlight(b)}"
if isinstance(b, TypeVariable):
return b.unify_internal(a, mode)
a, b = b, a
a.unify_internal(b, mode)
@abstractmethod
def contains_internal(self, other: "BaseType") -> bool:
pass
def contains(self, other: "BaseType") -> bool:
needle, haystack = other.resolve(), self.resolve()
return (needle is haystack) or haystack.contains_internal(needle)
def try_assign(self, other: "BaseType") -> bool:
try:
self.unify(other)
return True
except:
return False
cur_var = 0 cur_var = 0
...@@ -52,7 +96,7 @@ class ConcreteType(BaseType): ...@@ -52,7 +96,7 @@ class ConcreteType(BaseType):
@dataclass(eq=False) @dataclass(eq=False)
class TypeVariable(ConcreteType): class TypeVariable(ConcreteType):
name: str = field(default_factory=lambda: next_var_id()) var_name: str = field(default_factory=lambda: next_var_id())
resolved: Optional[ConcreteType] = None resolved: Optional[ConcreteType] = None
def resolve(self) -> ConcreteType: def resolve(self) -> ConcreteType:
...@@ -60,19 +104,30 @@ class TypeVariable(ConcreteType): ...@@ -60,19 +104,30 @@ class TypeVariable(ConcreteType):
return self return self
return self.resolved.resolve() return self.resolved.resolve()
def __str__(self): def name(self):
if self.resolved is None: if self.resolved is None:
# return f"TypeVar[\"{self.name}\"]" # return f"TypeVar[\"{self.name}\"]"
return f"_{self.name}" return f"_{self.var_name}"
return str(self.resolved) return str(self.resolved)
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, BaseType): if not isinstance(other, BaseType):
return False return False
if self.resolved is None: if self.resolved is None:
return self == other return self is other
return self.resolved == other.resolve() return self.resolved == other.resolve()
def unify_internal(self, other: BaseType, mode: UnifyMode):
if self is not other:
if other.contains(self):
from transpiler.phases.typing.exceptions import RecursiveTypeUnificationError
raise RecursiveTypeUnificationError(self, other)
self.resolved = other
def contains_internal(self, other: BaseType) -> bool:
return self.resolve() is other.resolve()
@dataclass(eq=False) @dataclass(eq=False)
class ResolvedConcreteType(ConcreteType): class ResolvedConcreteType(ConcreteType):
...@@ -94,18 +149,22 @@ class ResolvedConcreteType(ConcreteType): ...@@ -94,18 +149,22 @@ class ResolvedConcreteType(ConcreteType):
https://www.python.org/download/releases/2.3/mro/ https://www.python.org/download/releases/2.3/mro/
""" """
def remove_head_if(lst, head):
if lst[0] == head:
return lst[1:]
return lst
def merge(*lists): def merge(*lists):
while True:
lists = [l for l in lists if len(l) > 0] lists = [l for l in lists if len(l) > 0]
if len(lists) == 0:
return []
for i, l in enumerate(lists): for i, l in enumerate(lists):
first = l[0] head = l[0]
for j, l2 in enumerate(lists): if not any(head in x[1:] for j, x in enumerate(lists) if i != j):
if j == i: return [head] + merge(*[remove_head_if(x, head) for x in lists])
continue
if first in l2:
break
else: else:
return [first] + merge(*[x[1:] for x in lists if x[0] != first]) # couldn't find good head
# unable to find a next element
from transpiler.phases.typing.exceptions import InconsistentMroError from transpiler.phases.typing.exceptions import InconsistentMroError
raise InconsistentMroError(self.parents) raise InconsistentMroError(self.parents)
...@@ -114,10 +173,34 @@ class ResolvedConcreteType(ConcreteType): ...@@ -114,10 +173,34 @@ class ResolvedConcreteType(ConcreteType):
def inherits(self, parent: BaseType): def inherits(self, parent: BaseType):
return self == parent or any(p.inherits(parent) for p in self.parents) return self == parent or any(p.inherits(parent) for p in self.parents)
def can_receive(self, value: BaseType):
return self == value
class UniqueTypeMixin:
def unify_internal(self, other: "BaseType", mode: UnifyMode):
from transpiler.phases.typing.exceptions import TypeMismatchError, TypeMismatchKind
if other != self:
raise TypeMismatchError(self, other, TypeMismatchKind.DIFFERENT_TYPE)
def contains_internal(self, other: "BaseType") -> bool:
return self == other
@dataclass(eq=False) @dataclass(eq=False)
class BuiltinType(ResolvedConcreteType): class BuiltinType(UniqueTypeMixin, ResolvedConcreteType):
pass pass
@dataclass
class SpecialConstantType[T](ConcreteType):
value: T
def name(self):
return str(self.value)
def __eq__(self, other):
if isinstance(other, SpecialConstantType):
return self.value == other.value
return False
@dataclass(eq=False, init=False) @dataclass(eq=False, init=False)
class GenericInstanceType(ResolvedConcreteType): class GenericInstanceType(ResolvedConcreteType):
...@@ -140,8 +223,23 @@ class GenericInstanceType(ResolvedConcreteType): ...@@ -140,8 +223,23 @@ class GenericInstanceType(ResolvedConcreteType):
return self.generic_parent == other.generic_parent and self.generic_args == other.generic_args return self.generic_parent == other.generic_parent and self.generic_args == other.generic_args
return False return False
def __str__(self): def name(self):
return f"{self.generic_parent}[{', '.join(map(str, self.generic_args))}]" return f"{self.generic_parent.name()}[{', '.join(map(str, self.generic_args))}]"
def unify_internal(self, other: "BaseType", mode: UnifyMode):
from transpiler.phases.typing.exceptions import TypeMismatchError, TypeMismatchKind
if not isinstance(other, GenericInstanceType):
raise TypeMismatchError(self, other, TypeMismatchKind.DIFFERENT_TYPE)
if self.generic_parent != other.generic_parent:
raise TypeMismatchError(self, other, TypeMismatchKind.DIFFERENT_TYPE)
if len(self.generic_args) != len(other.generic_args):
raise TypeMismatchError(self, other, TypeMismatchKind.DIFFERENT_TYPE)
for a, b in zip(self.generic_args, other.generic_args):
a.unify(b, mode)
def contains_internal(self, other: "BaseType") -> bool:
# is this correct?
return self == other or any(a.contains(other) for a in self.generic_args)
class GenericParameterKind(enum.Enum): class GenericParameterKind(enum.Enum):
NORMAL = enum.auto() NORMAL = enum.auto()
...@@ -176,13 +274,24 @@ class GenericType(BaseType): ...@@ -176,13 +274,24 @@ class GenericType(BaseType):
res.generic_parent = self res.generic_parent = self
return res return res
def instantiate_default(self) -> GenericInstanceType:
return self.instantiate([TypeVariable() for _ in self.parameters])
def __str__(self):
try:
return str(self.instantiate_default())
except:
return super().__str__()
@dataclass(eq=False, init=False) @dataclass(eq=False, init=False)
class BuiltinGenericType(GenericType): class BuiltinGenericType(UniqueTypeMixin, GenericType):
constraints_: Callable[[list[ConcreteType]], list[GenericConstraint]] constraints_: Callable[[list[ConcreteType]], list[GenericConstraint]]
instantiate_: Callable[[list[ConcreteType]], GenericInstanceType] instantiate_: Callable[[list[ConcreteType]], GenericInstanceType]
def constraints(self, args: list[ConcreteType]) -> list[GenericConstraint]: def constraints(self, args: list[ConcreteType]) -> list[GenericConstraint]:
if not hasattr(self, "constraints_"):
raise ValueError("missing constraints_")
return self.constraints_(args) return self.constraints_(args)
def _instantiate(self, args: list[ConcreteType]) -> GenericInstanceType: def _instantiate(self, args: list[ConcreteType]) -> GenericInstanceType:
...@@ -191,7 +300,7 @@ class BuiltinGenericType(GenericType): ...@@ -191,7 +300,7 @@ class BuiltinGenericType(GenericType):
def create_builtin_type(name: str): def create_builtin_type(name: str):
class CreatedType(BuiltinType): class CreatedType(BuiltinType):
def __str__(self): def name(self):
return name return name
CreatedType.__name__ = f"BuiltinType${name}" CreatedType.__name__ = f"BuiltinType${name}"
res = CreatedType() res = CreatedType()
...@@ -216,18 +325,24 @@ def unimpl(*args, **kwargs): ...@@ -216,18 +325,24 @@ def unimpl(*args, **kwargs):
def create_builtin_generic_type(name: str): def create_builtin_generic_type(name: str):
class CreatedType(BuiltinGenericType): class CreatedType(BuiltinGenericType):
def __str__(self): def name(self):
return name return name
CreatedType.__name__ = f"BuiltinGenericType${name}" CreatedType.__name__ = f"BuiltinGenericType${name}"
res = CreatedType() res = CreatedType()
return res return res
@dataclass(eq=False)
class TupleInstanceType(GenericInstanceType):
fields: list[ConcreteType]
TY_LIST = create_builtin_generic_type("list") TY_LIST = create_builtin_generic_type("list")
TY_SET = create_builtin_generic_type("set") TY_SET = create_builtin_generic_type("set")
TY_DICT = create_builtin_generic_type("dict") TY_DICT = create_builtin_generic_type("dict")
TY_TUPLE = create_builtin_generic_type("tuple") TY_TUPLE = create_builtin_generic_type("tuple")
TY_TUPLE.instantiate_ = lambda args: TupleInstanceType(args)
@dataclass(unsafe_hash=False) @dataclass(unsafe_hash=False)
class TypeTupleType(ConcreteType): class TypeTupleType(ConcreteType):
""" """
...@@ -239,9 +354,18 @@ class TypeTupleType(ConcreteType): ...@@ -239,9 +354,18 @@ class TypeTupleType(ConcreteType):
""" """
contents: list[ConcreteType] contents: list[ConcreteType]
def __str__(self): def name(self):
return f"*[{', '.join(map(str, self.contents))}]" return f"*[{', '.join(map(str, self.contents))}]"
def unify_internal(self, other: "BaseType", mode: UnifyMode):
from transpiler.phases.typing.exceptions import TypeMismatchError, TypeMismatchKind
if type(other) != TypeTupleType:
raise TypeMismatchError(self, other, TypeMismatchKind.DIFFERENT_TYPE)
if len(self.contents) != len(other.contents):
raise TypeMismatchError(self, other, TypeMismatchKind.DIFFERENT_TYPE)
for a, b in zip(self.contents, other.contents):
a.unify(b, mode)
@dataclass(unsafe_hash=False) @dataclass(unsafe_hash=False)
class TypeListType(ConcreteType): class TypeListType(ConcreteType):
""" """
...@@ -251,17 +375,47 @@ class TypeListType(ConcreteType): ...@@ -251,17 +375,47 @@ class TypeListType(ConcreteType):
""" """
contents: list[ConcreteType] contents: list[ConcreteType]
def __str__(self): def name(self):
return f"[{', '.join(map(str, self.contents))}]" return f"[{', '.join(map(str, self.contents))}]"
def unify_internal(self, other: "BaseType", mode: UnifyMode):
from transpiler.phases.typing.exceptions import TypeMismatchError, TypeMismatchKind
if type(other) != TypeListType:
raise TypeMismatchError(self, other, TypeMismatchKind.DIFFERENT_TYPE)
if len(self.contents) != len(other.contents):
raise TypeMismatchError(self, other, TypeMismatchKind.DIFFERENT_TYPE)
for a, b in zip(self.contents, other.contents):
a.unify(b, mode)
def contains_internal(self, other: "BaseType") -> bool:
return self == other or any(a.contains(other) for a in self.contents)
@dataclass(eq=False)
class UnionInstanceType(GenericInstanceType):
types: list[ConcreteType]
class UnionType(UniqueTypeMixin, GenericType):
def name(self):
return "Union"
def _instantiate(self, args: list[ConcreteType]) -> GenericInstanceType:
return UnionInstanceType(args)
TY_UNION = UnionType()
@dataclass(eq=False) @dataclass(eq=False)
class CallableInstanceType(GenericInstanceType): class CallableInstanceType(GenericInstanceType):
parameters: list[ConcreteType] parameters: list[ConcreteType]
return_type: ConcreteType return_type: ConcreteType
def remove_self(self):
return self.generic_parent.instantiate([TypeListType(self.parameters[1:]), self.return_type])
class CallableType(GenericType):
def __str__(self):
class CallableType(UniqueTypeMixin, GenericType):
def name(self):
return "Callable" return "Callable"
def _instantiate(self, args: list[ConcreteType]) -> CallableInstanceType: def _instantiate(self, args: list[ConcreteType]) -> CallableInstanceType:
...@@ -271,6 +425,47 @@ class CallableType(GenericType): ...@@ -271,6 +425,47 @@ class CallableType(GenericType):
case _: case _:
raise ValueError raise ValueError
@dataclass(eq=False, init=False)
class LambdaInstanceType(CallableInstanceType):
def __str__(self):
return f"<lambda>({", ".join(map(str, self.parameters))}) -> {self.return_type}"
class LambdaType(CallableType):
def name(self):
return "<lambda>"
def _instantiate(self, args: list[ConcreteType]) -> CallableInstanceType:
match args:
case [TypeListType([*args]), ret]:
return CallableInstanceType(args, ret)
case _:
raise ValueError
TY_LAMBDA = LambdaType()
def make_builtin_feature(name: str):
class CreatedType(BuiltinType):
def name(self):
return name
def feature(self):
return name
CreatedType.__name__ = f"BuiltinFeatureType${name}"
return CreatedType()
def make_cpp_type(name: str):
class CreatedType(BuiltinType):
def name(self):
return name
CreatedType.__name__ = f"CppTypeType${name}"
return CreatedType()
TY_BUILTIN_FEATURE = create_builtin_generic_type("BuiltinFeature")
TY_CPP_TYPE = create_builtin_type("CppType")
TY_CALLABLE = CallableType() TY_CALLABLE = CallableType()
...@@ -278,8 +473,8 @@ TY_CALLABLE = CallableType() ...@@ -278,8 +473,8 @@ TY_CALLABLE = CallableType()
class ClassTypeType(GenericInstanceType): class ClassTypeType(GenericInstanceType):
inner_type: BaseType inner_type: BaseType
class ClassType(GenericType): class ClassType(UniqueTypeMixin, GenericType):
def __str__(self): def name(self):
return "Type" return "Type"
def _instantiate(self, args: list[ConcreteType]) -> ClassTypeType: def _instantiate(self, args: list[ConcreteType]) -> ClassTypeType:
......
import ast
from pathlib import Path
from logging import debug
from transpiler.phases.typing.scope import VarKind, VarDecl, ScopeKind, Scope
from transpiler.phases.typing.stdlib import PRELUDE, StdlibVisitor
from transpiler.phases.typing.types import TY_TYPE, TY_INT, TY_STR, TY_BOOL, TY_COMPLEX, TY_NONE, FunctionType, \
TypeVariable, CppType, PyList, TypeType, Forked, Task, Future, PyIterator, TupleType, TypeOperator, BaseType, \
ModuleType, TY_BYTES, TY_FLOAT, PyDict, TY_SLICE, TY_OBJECT, BuiltinFeature, UnionType, MemberDef
PRELUDE.vars.update({
# "int": VarDecl(VarKind.LOCAL, TY_TYPE, TY_INT),
# "str": VarDecl(VarKind.LOCAL, TY_TYPE, TY_STR),
# "bool": VarDecl(VarKind.LOCAL, TY_TYPE, TY_BOOL),
# "complex": VarDecl(VarKind.LOCAL, TY_TYPE, TY_COMPLEX),
# "None": VarDecl(VarKind.LOCAL, TY_NONE, None),
# "Callable": VarDecl(VarKind.LOCAL, TY_TYPE, FunctionType),
# "TypeVar": VarDecl(VarKind.LOCAL, TY_TYPE, TypeVariable),
# "CppType": VarDecl(VarKind.LOCAL, TY_TYPE, CppType),
# "list": VarDecl(VarKind.LOCAL, TY_TYPE, PyList),
"int": VarDecl(VarKind.LOCAL, TypeType(TY_INT)),
"float": VarDecl(VarKind.LOCAL, TypeType(TY_FLOAT)),
"str": VarDecl(VarKind.LOCAL, TypeType(TY_STR)),
"bytes": VarDecl(VarKind.LOCAL, TypeType(TY_BYTES)),
"bool": VarDecl(VarKind.LOCAL, TypeType(TY_BOOL)),
"complex": VarDecl(VarKind.LOCAL, TypeType(TY_COMPLEX)),
"None": VarDecl(VarKind.LOCAL, TypeType(TY_NONE)),
"Callable": VarDecl(VarKind.LOCAL, TypeType(FunctionType)),
#"TypeVar": VarDecl(VarKind.LOCAL, TypeType(TypeVariable)),
"CppType": VarDecl(VarKind.LOCAL, TypeType(CppType)),
"list": VarDecl(VarKind.LOCAL, TypeType(PyList)),
"dict": VarDecl(VarKind.LOCAL, TypeType(PyDict)),
"Forked": VarDecl(VarKind.LOCAL, TypeType(Forked)),
"Task": VarDecl(VarKind.LOCAL, TypeType(Task)),
"Future": VarDecl(VarKind.LOCAL, TypeType(Future)),
"Iterator": VarDecl(VarKind.LOCAL, TypeType(PyIterator)),
"tuple": VarDecl(VarKind.LOCAL, TypeType(TupleType)),
"slice": VarDecl(VarKind.LOCAL, TypeType(TY_SLICE)),
"object": VarDecl(VarKind.LOCAL, TypeType(TY_OBJECT)),
"BuiltinFeature": VarDecl(VarKind.LOCAL, TypeType(BuiltinFeature)),
"Any": VarDecl(VarKind.LOCAL, TypeType(TY_OBJECT)),
"Optional": VarDecl(VarKind.LOCAL, TypeType(lambda x: UnionType(x, TY_NONE))),
})
typon_std = Path(__file__).parent.parent.parent.parent / "stdlib"
def make_module(name: str, scope: Scope) -> BaseType:
ty = ModuleType([], f"{name}")
for n, v in scope.vars.items():
ty.fields[n] = MemberDef(v.type, v.val, False)
return ty
def discover_module(path: Path, scope):
for child in sorted(path.iterdir()):
if child.is_dir():
mod_scope = PRELUDE.child(ScopeKind.GLOBAL)
discover_module(child, mod_scope)
scope.vars[child.name] = make_mod_decl(child.name, mod_scope)
elif child.name == "__init__.py":
StdlibVisitor(scope).visit(ast.parse(child.read_text()))
debug(f"Visited {child}")
elif child.suffix == ".py":
mod_scope = PRELUDE.child(ScopeKind.GLOBAL)
StdlibVisitor(mod_scope).visit(ast.parse(child.read_text()))
if child.stem[-1] == "_":
child = child.with_name(child.stem[:-1])
scope.vars[child.stem] = make_mod_decl(child.name, mod_scope)
debug(f"Visited {child}")
def make_mod_decl(child, mod_scope):
return VarDecl(VarKind.MODULE, make_module(child, mod_scope), {k: v.type for k, v in mod_scope.vars.items()})
discover_module(typon_std, PRELUDE)
debug("Stdlib visited!")
#exit()
\ No newline at end of file
import abc
import ast
from dataclasses import dataclass, field
from typing import Optional, List
from transpiler.phases.typing.scope import Scope
from transpiler.phases.typing.types import BaseType, TY_NONE, TypeVariable, TY_TYPE, ResolvedConcreteType, TypeListType
from transpiler.phases.utils import NodeVisitorSeq
@dataclass
class TypeAnnotationVisitor(NodeVisitorSeq):
scope: Scope
def visit_str(self, node: str) -> BaseType:
if existing := self.scope.get(node):
ty = existing.type
assert isinstance(ty, ResolvedConcreteType)
assert ty.inherits(TY_TYPE)
return ty.inner_type
from transpiler.phases.typing.exceptions import UnknownNameError
raise UnknownNameError(node)
def visit_Name(self, node: ast.Name) -> BaseType:
return self.visit_str(node.id)
def visit_Constant(self, node: ast.Constant) -> BaseType:
if node.value is None:
return TY_NONE
if type(node.value) == str:
return node.value
raise NotImplementedError
def visit_Subscript(self, node: ast.Subscript) -> BaseType:
# ty_op = self.visit(node.value)
# args = list(node.slice.elts) if type(node.slice) == ast.Tuple else [node.slice]
# args = [self.visit(arg) for arg in args]
# return ty_op(*args)
raise NotImplementedError()
# return TypeOperator([self.visit(node.value)], self.visit(node.slice.value))
def visit_List(self, node: ast.List) -> BaseType:
return TypeListType([self.visit(elt) for elt in node.elts])
def visit_Attribute(self, node: ast.Attribute) -> BaseType:
raise NotImplementedError()
# left = self.visit(node.value)
# res = left.fields[node.attr].type
# assert isinstance(res, TypeType)
# return res.type_object
def visit_BinOp(self, node: ast.BinOp) -> BaseType:
# if isinstance(node.op, ast.BitOr):
# return UnionType(self.visit(node.left), self.visit(node.right))
raise NotImplementedError(node.op)
import ast
import copy
import dataclasses
import importlib
from dataclasses import dataclass
from transpiler.exceptions import CompileError
from transpiler.utils import highlight, linenodata
from transpiler.phases.typing import make_mod_decl
from transpiler.phases.typing.common import ScoperVisitor, get_iter, get_next, is_builtin
from transpiler.phases.typing.expr import ScoperExprVisitor, DUNDER
from transpiler.phases.typing.class_ import ScoperClassVisitor
from transpiler.phases.typing.scope import VarDecl, VarKind, ScopeKind, Scope
from transpiler.phases.typing.types import BaseType, TypeVariable, FunctionType, \
Promise, TY_NONE, PromiseKind, TupleType, UserType, TypeType, ModuleType, BuiltinFeature, TY_INT, MemberDef, \
RuntimeValue, GenericUserType, MonomorphizedUserType
from transpiler.phases.utils import PlainBlock, AnnotationName
@dataclass
class ScoperBlockVisitor(ScoperVisitor):
stdlib: bool = False
def visit_Pass(self, node: ast.Pass):
pass
def get_module(self, name: str) -> VarDecl:
mod = self.scope.get(name, VarKind.MODULE)
if mod is None:
# try lookup with importlib
py_mod = importlib.import_module(name)
mod_scope = Scope()
# copy all functions to mod_scope
for fname, obj in py_mod.__dict__.items():
if callable(obj):
# fty = FunctionType([], TypeVariable())
# fty.is_python_func = True
fty = TypeVariable()
fty.is_python_func = True
mod_scope.vars[fname] = VarDecl(VarKind.LOCAL, fty)
mod = make_mod_decl(name, mod_scope)
mod.type.is_python = True
self.scope.vars[name] = mod
if mod is None:
from transpiler.phases.typing.exceptions import UnknownNameError
raise UnknownNameError(name)
assert isinstance(mod, VarDecl), mod
assert isinstance(mod.type, ModuleType), mod.type
return mod
def visit_Import(self, node: ast.Import):
for alias in node.names:
mod = self.get_module(alias.name)
alias.module_obj = mod.type
self.scope.vars[alias.asname or alias.name] = dataclasses.replace(mod, kind=VarKind.LOCAL)
def visit_ImportFrom(self, node: ast.ImportFrom):
if node.module in {"typing2", "__future__"}:
return
module = self.get_module(node.module)
node.module_obj = module.type
for alias in node.names:
thing = module.val.get(alias.name)
if not thing:
from transpiler.phases.typing.exceptions import UnknownModuleMemberError
raise UnknownModuleMemberError(node.module, alias.name)
alias.item_obj = thing
self.scope.vars[alias.asname or alias.name] = VarDecl(VarKind.LOCAL, thing)
def visit_Module(self, node: ast.Module):
self.visit_block(node.body)
def get_type(self, node: ast.expr) -> BaseType:
if type := getattr(node, "type", None):
return type
self.expr().visit(node)
return node.type
# ntype = TypeVariable()
# node.type = ntype
# return ntype
def visit_Assign(self, node: ast.Assign):
if len(node.targets) != 1:
raise NotImplementedError(node)
target = node.targets[0]
ty = self.get_type(node.value)
decl = self.visit_assign_target(target, ty)
if not hasattr(node, "is_declare"):
node.is_declare = decl
def visit_AnnAssign(self, node: ast.AnnAssign):
if node.simple != 1:
raise NotImplementedError(node)
if not isinstance(node.target, ast.Name):
raise NotImplementedError(node)
ty = self.visit_annotation(node.annotation)
decl = self.visit_assign_target(node.target, ty)
if not hasattr(node, "is_declare"):
node.is_declare = decl
if node.value is not None:
ty_val = self.get_type(node.value)
__TB__ = f"unifying annotation {highlight(node.annotation)} with value {highlight(node.value)} of type {highlight(ty_val)}"
ty.unify(ty_val)
def visit_assign_target(self, target, decl_val: BaseType) -> bool:
__TB__ = f"analyzing assignment target {highlight(target)} with value {highlight(decl_val)}"
if isinstance(target, ast.Name):
if target.id == "_":
return False
target.type = decl_val
if vdecl := self.scope.get(target.id, {VarKind.LOCAL, VarKind.GLOBAL, VarKind.NONLOCAL}, restrict_function=True):
__TB__ = f"unifying existing variable {highlight(target.id)} of type {highlight(vdecl.type)} with assigned value {highlight(decl_val)}"
vdecl.type.unify(decl_val)
return False
else:
self.scope.vars[target.id] = VarDecl(VarKind.LOCAL, decl_val)
if self.scope.kind == ScopeKind.FUNCTION_INNER:
self.root_decls[target.id] = VarDecl(VarKind.OUTER_DECL, decl_val)
return False
return True
elif isinstance(target, ast.Tuple):
if not isinstance(decl_val, TupleType):
from transpiler.phases.typing.exceptions import InvalidUnpackError
raise InvalidUnpackError(decl_val)
if len(target.elts) != len(decl_val.args):
from transpiler.phases.typing.exceptions import InvalidUnpackCountError
raise InvalidUnpackCountError(decl_val, len(target.elts))
target.type = decl_val
decls = [self.visit_assign_target(t, ty) for t, ty in zip(target.elts, decl_val.args)] # eager evaluated
return decls
elif isinstance(target, ast.Attribute):
attr_type = self.expr().visit(target)
attr_type.unify(decl_val)
return False
elif isinstance(target, ast.Subscript):
expr = self.expr()
left = expr.visit(target.value)
args = target.slice if type(target.slice) == tuple else [target.slice]
args = [expr.visit(e) for e in args]
if len(args) == 1:
args = args[0]
expr.make_dunder([left, args, decl_val], "setitem")
return False
else:
raise NotImplementedError(ast.unparse(target))
def visit_FunctionDef(self, node: ast.FunctionDef):
ftype = self.parse_function(node)
ftype.return_type = Promise(ftype.return_type, PromiseKind.TASK)
self.scope.vars[node.name] = VarDecl(VarKind.LOCAL, ftype)
def process_class_ast(self, ctype: BaseType, node: ast.ClassDef, bases_after: list[ast.expr]):
scope = self.scope.child(ScopeKind.CLASS)
scope.obj_type = ctype
scope.class_ = scope
node.inner_scope = scope
node.type = ctype
visitor = ScoperClassVisitor(scope, cur_class=TypeType(ctype))
visitor.visit_block(node.body)
for base in bases_after:
base = self.expr().visit(base)
if is_builtin(base, "Enum"):
ctype.parents.append(TY_INT)
for k, m in ctype.fields.items():
m.type = ctype
m.val = ast.literal_eval(m.val)
assert type(m.val) == int
ctype.fields["value"] = MemberDef(TY_INT)
lnd = linenodata(node)
init_method = ast.FunctionDef(
name="__init__",
args=ast.arguments(
args=[ast.arg(arg="self"), ast.arg(arg="value")],
defaults=[],
kw_defaults=[],
kwarg=None,
kwonlyargs=[],
posonlyargs=[],
),
body=[
ast.Assign(
targets=[ast.Attribute(value=ast.Name(id="self"), attr="value")],
value=ast.Name(id="value"),
**lnd
)
],
decorator_list=[],
returns=None,
type_comment=None,
**lnd
)
_, rtype = visitor.visit_FunctionDef(init_method)
visitor.visit_function_definition(init_method, rtype)
node.body.append(init_method)
ctype.is_enum = True
else:
raise NotImplementedError(base)
for deco in node.decorator_list:
deco = self.expr().visit(deco)
if is_builtin(deco, "dataclass"):
# init_type = FunctionType([cttype, *cttype.members.values()], TypeVariable())
# cttype.methods["__init__"] = init_type
lnd = linenodata(node)
init_method = ast.FunctionDef(
name="__init__",
args=ast.arguments(
args=[ast.arg(arg="self"), * [ast.arg(arg=n) for n in ctype.get_members()]],
defaults=[],
kw_defaults=[],
kwarg=None,
kwonlyargs=[],
posonlyargs=[],
),
body=[
ast.Assign(
targets=[ast.Attribute(value=ast.Name(id="self"), attr=n)],
value=ast.Name(id=n),
**lnd
) for n in ctype.get_members()
],
decorator_list=[],
returns=None,
type_comment=None,
**lnd
)
_, rtype = visitor.visit_FunctionDef(init_method)
visitor.visit_function_definition(init_method, rtype)
node.body.append(init_method)
else:
raise NotImplementedError(deco)
return ctype
def visit_ClassDef(self, node: ast.ClassDef):
copied = copy.deepcopy(node)
class NewUserType(UserType):
def __init__(self):
super().__init__(node.name)
#ctype = UserType(node.name)
typevars = []
bases_after = []
for base in node.bases:
if isinstance(base, ast.Subscript):
if isinstance(base.slice, ast.Name):
sliceval = [base.slice.id]
elif isinstance(base.slice, ast.Tuple):
sliceval = [n.id for n in base.slice.elts]
if is_builtin(self.expr().visit(base.value), "Generic"):
typevars = sliceval
else:
bases_after.append(base)
if typevars:
# generic
#ctype = GenericUserType(node.name, typevars, node)
var_scope = self.scope.child(ScopeKind.GLOBAL)
var_visitor = ScoperBlockVisitor(var_scope, self.root_decls)
node.gen_instances = {}
class OurGenericType(GenericUserType):
# def __init__(self, *args):
# super().__init__(node.name)
# for tv, arg in zip(typevars, args):
# var_scope.declare_local(tv, arg)
# var_visitor.process_class_ast(self, node, bases_after)
def __new__(cls, *args, **kwargs):
res = MonomorphizedUserType(node.name + "$$" + "__".join(map(str, args)) + "$$")
for tv, arg in zip(typevars, args):
var_scope.declare_local(tv, arg)
new_node = copy.deepcopy(copied)
new_node.name = res.name
var_visitor.process_class_ast(res, new_node, bases_after)
node.gen_instances[tuple(args)] = new_node
return res
ctype = OurGenericType
else:
# not generic
ctype = self.process_class_ast(UserType(node.name), node, bases_after)
cttype = TypeType(ctype)
self.scope.vars[node.name] = VarDecl(VarKind.LOCAL, cttype)
def visit_If(self, node: ast.If):
scope = self.scope.child(ScopeKind.FUNCTION_INNER)
node.inner_scope = scope
self.expr().visit(node.test)
then_scope = scope.child(ScopeKind.FUNCTION_INNER)
then_visitor = ScoperBlockVisitor(then_scope, self.root_decls)
then_visitor.visit_block(node.body)
if node.orelse:
else_scope = scope.child(ScopeKind.FUNCTION_INNER)
else_visitor = ScoperBlockVisitor(else_scope, self.root_decls)
else_visitor.visit_block(node.orelse)
node.orelse_scope = else_scope
if then_scope.diverges and else_scope.diverges:
self.scope.diverges = True
def visit_While(self, node: ast.While):
scope = self.scope.child(ScopeKind.FUNCTION_INNER)
scope.is_loop = node
node.inner_scope = scope
self.expr().visit(node.test)
body_scope = scope.child(ScopeKind.FUNCTION_INNER)
body_visitor = ScoperBlockVisitor(body_scope, self.root_decls)
body_visitor.visit_block(node.body)
if node.orelse:
orelse_scope = scope.child(ScopeKind.FUNCTION_INNER)
orelse_visitor = ScoperBlockVisitor(orelse_scope, self.root_decls)
orelse_visitor.visit_block(node.orelse)
node.orelse_variable = f"orelse_{id(node)}"
def visit_PlainBlock(self, node: PlainBlock):
scope = self.scope.child(ScopeKind.FUNCTION_INNER)
node.inner_scope = scope
body_visitor = ScoperBlockVisitor(scope, self.root_decls)
body_visitor.visit_block(node.body)
def visit_For(self, node: ast.For):
scope = self.scope.child(ScopeKind.FUNCTION_INNER)
scope.is_loop = node
node.inner_scope = scope
assert isinstance(node.target, ast.Name)
var_var = TypeVariable()
scope.vars[node.target.id] = VarDecl(VarKind.LOCAL, var_var)
seq_type = self.expr().visit(node.iter)
iter_type = get_iter(seq_type)
next_type = get_next(iter_type)
var_var.unify(next_type)
body_scope = scope.child(ScopeKind.FUNCTION_INNER)
body_visitor = ScoperBlockVisitor(body_scope, self.root_decls)
body_visitor.visit_block(node.body)
if node.orelse:
orelse_scope = scope.child(ScopeKind.FUNCTION_INNER)
orelse_visitor = ScoperBlockVisitor(orelse_scope, self.root_decls)
orelse_visitor.visit_block(node.orelse)
node.orelse_variable = f"orelse_{id(node)}"
def visit_Expr(self, node: ast.Expr):
self.expr().visit(node.value)
def visit_Return(self, node: ast.Return):
fct = self.scope.function
if fct is None:
from transpiler.phases.typing.exceptions import OutsideFunctionError
raise OutsideFunctionError()
ftype = fct.obj_type
assert isinstance(ftype, FunctionType)
vtype = self.expr().visit(node.value) if node.value else TY_NONE
vtype.unify(ftype.return_type.return_type if isinstance(ftype.return_type, Promise) else ftype.return_type)
self.scope.diverges = True
#fct.has_return = True
def visit_Global(self, node: ast.Global):
for name in node.names:
self.scope.function.vars[name] = VarDecl(VarKind.GLOBAL, None)
if name not in self.scope.global_scope.vars:
self.scope.global_scope.vars[name] = VarDecl(VarKind.LOCAL, None)
def visit_Nonlocal(self, node: ast.Global):
fct = self.scope.function
if fct is None:
from transpiler.phases.typing.exceptions import OutsideFunctionError
raise OutsideFunctionError()
for name in node.names:
fct.vars[name] = VarDecl(VarKind.NONLOCAL, None)
if name not in fct.parent.vars:
fct.parent.vars[name] = VarDecl(VarKind.LOCAL, None)
def visit_AugAssign(self, node: ast.AugAssign):
target, value = map(self.get_type, (node.target, node.value))
try:
self.expr().make_dunder([target, value], "i" + DUNDER[type(node.op)])
except CompileError as e:
self.visit_assign_target(node.target, self.expr().make_dunder([target, value], DUNDER[type(node.op)]))
# equivalent = ast.Assign(
# targets=[node.target],
# value=ast.BinOp(left=node.target, op=node.op, right=node.value, **linenodata(node)),
# **linenodata(node))
# self.visit(equivalent)
def visit(self, node: ast.AST):
if isinstance(node, ast.AST):
__TB_SKIP__ = True
super().visit(node)
node.scope = self.scope
else:
raise NotImplementedError(node)
def visit_Break(self, _node: ast.Break):
if not self.scope.is_in_loop():
from transpiler.phases.typing.exceptions import OutsideLoopError
raise OutsideLoopError()
def visit_Continue(self, _node: ast.Continue):
if not self.scope.is_in_loop():
from transpiler.phases.typing.exceptions import OutsideLoopError
raise OutsideLoopError()
def visit_Try(self, node: ast.Try):
scope = self.scope.child(ScopeKind.FUNCTION_INNER)
node.inner_scope = scope
body_scope = scope.child(ScopeKind.FUNCTION_INNER)
body_visitor = ScoperBlockVisitor(body_scope, self.root_decls)
body_visitor.visit_block(node.body)
# todo
for handler in node.handlers:
handler_scope = scope.child(ScopeKind.FUNCTION_INNER)
handler_visitor = ScoperBlockVisitor(handler_scope, self.root_decls)
handler_visitor.visit_block(handler.body)
if node.orelse:
else_scope = scope.child(ScopeKind.FUNCTION_INNER)
else_visitor = ScoperBlockVisitor(else_scope, self.root_decls)
else_visitor.visit_block(node.orelse)
if node.finalbody:
raise NotImplementedError(node.finalbody)
def visit_Raise(self, node: ast.Raise):
self.scope.diverges = True
if node.exc:
self.expr().visit(node.exc)
if node.cause:
self.expr().visit(node.cause)
\ No newline at end of file
# coding: utf-8
import ast
from dataclasses import dataclass, field
from transpiler.phases.typing import FunctionType, ScopeKind, VarDecl, VarKind, TY_NONE
from transpiler.phases.typing.common import ScoperVisitor
from transpiler.phases.typing.types import PromiseKind, Promise, BaseType, MemberDef
@dataclass
class ScoperClassVisitor(ScoperVisitor):
fdecls: list[(ast.FunctionDef, BaseType)] = field(default_factory=list)
def visit_AnnAssign(self, node: ast.AnnAssign):
assert node.value is None, "Class field should not have a value"
assert node.simple == 1, "Class field should be simple (identifier, not parenthesized)"
assert isinstance(node.target, ast.Name)
self.scope.obj_type.fields[node.target.id] = MemberDef(self.visit_annotation(node.annotation))
def visit_Assign(self, node: ast.Assign):
assert len(node.targets) == 1, "Can't use destructuring in class static member"
assert isinstance(node.targets[0], ast.Name)
node.is_declare = True
valtype = self.expr().visit(node.value)
node.targets[0].type = valtype
self.scope.obj_type.fields[node.targets[0].id] = MemberDef(valtype, node.value)
def visit_FunctionDef(self, node: ast.FunctionDef):
ftype = self.parse_function(node)
ftype.parameters[0].unify(self.scope.obj_type)
inner = ftype.return_type
if node.name != "__init__":
ftype.return_type = Promise(ftype.return_type, PromiseKind.TASK)
ftype.is_method = True
self.scope.obj_type.fields[node.name] = MemberDef(ftype, node)
return (node, inner)
import ast
from dataclasses import dataclass, field
from typing import Dict, Optional
from transpiler.utils import highlight
from transpiler.phases.typing.annotations import TypeAnnotationVisitor
from transpiler.phases.typing.scope import Scope, ScopeKind, VarDecl, VarKind
from transpiler.phases.typing.types import BaseType, TypeVariable, TY_NONE, TypeType, BuiltinFeature, FunctionType, \
Promise, PromiseKind
from transpiler.phases.utils import NodeVisitorSeq, AnnotationName
PRELUDE = Scope.make_global()
@dataclass
class ScoperVisitor(NodeVisitorSeq):
scope: Scope = field(default_factory=lambda: PRELUDE.child(ScopeKind.GLOBAL))
root_decls: Dict[str, VarDecl] = field(default_factory=dict)
cur_class: Optional[TypeType] = None
def expr(self) -> "ScoperExprVisitor":
from transpiler.phases.typing.expr import ScoperExprVisitor
return ScoperExprVisitor(self.scope, self.root_decls)
def anno(self) -> "TypeAnnotationVisitor":
return TypeAnnotationVisitor(self.scope, self.cur_class)
def visit_annotation(self, expr: Optional[ast.expr]) -> BaseType:
res = self.anno().visit(expr) if expr else TypeVariable()
assert not isinstance(res, TypeType)
return res
def annotate_arg(self, arg: ast.arg) -> BaseType:
if arg.annotation is None or isinstance(arg.annotation, AnnotationName):
res = TypeVariable()
arg.annotation = AnnotationName(res)
return res
else:
return self.visit_annotation(arg.annotation)
def parse_function(self, node: ast.FunctionDef):
argtypes = [self.annotate_arg(arg) for arg in node.args.args]
rtype = self.visit_annotation(node.returns)
ftype = FunctionType(argtypes, rtype)
scope = self.scope.child(ScopeKind.FUNCTION)
scope.obj_type = ftype
scope.function = scope
node.inner_scope = scope
node.type = ftype
ftype.optional_at = len(node.args.args) - len(node.args.defaults)
for ty, default in zip(argtypes[ftype.optional_at:], node.args.defaults):
self.expr().visit(default).unify(ty)
for arg, ty in zip(node.args.args, argtypes):
scope.vars[arg.arg] = VarDecl(VarKind.LOCAL, ty)
self.fdecls.append((node, rtype))
return ftype
def visit_block(self, block: list[ast.AST]):
if not block:
return
__TB__ = f"running type analysis on block starting with {highlight(block[0])}"
self.fdecls = []
for b in block:
self.visit(b)
if self.fdecls:
old_list = self.fdecls
exc = None
while True:
new_list = []
for node, rtype in old_list:
from transpiler.exceptions import CompileError
try:
self.visit_function_definition(node, rtype)
except CompileError as e:
new_list.append((node, rtype))
if not exc or getattr(node, "is_main", False):
exc = e
if len(new_list) == len(old_list):
raise exc
if not new_list:
break
old_list = new_list
exc = None
def visit_function_definition(self, node, rtype):
__TB__ = f"running type analysis on the body of {highlight(node)}"
__TB_NODE__ = node
from transpiler.phases.typing.block import ScoperBlockVisitor
for b in node.body:
decls = {}
visitor = ScoperBlockVisitor(node.inner_scope, decls)
visitor.fdecls = []
visitor.visit(b)
if len(visitor.fdecls) > 1:
raise NotImplementedError("?")
elif len(visitor.fdecls) == 1:
fnode, frtype = visitor.fdecls[0]
self.visit_function_definition(fnode, frtype)
#del node.inner_scope.vars[fnode.name]
visitor.visit_assign_target(ast.Name(fnode.name), fnode.type)
b.decls = decls
if not node.inner_scope.diverges and not (isinstance(node.type.return_type, Promise) and node.type.return_type.kind == PromiseKind.GENERATOR):
from transpiler.phases.typing.exceptions import TypeMismatchError
try:
rtype.unify(TY_NONE)
except TypeMismatchError as e:
from transpiler.phases.typing.exceptions import MissingReturnError
raise MissingReturnError(node) from e
def get_iter(seq_type):
try:
iter_type = seq_type.fields["__iter__"].type.return_type
except:
from transpiler.phases.typing.exceptions import NotIterableError
raise NotIterableError(seq_type)
return iter_type
def get_next(iter_type):
try:
next_type = iter_type.fields["__next__"].type.return_type
except:
from transpiler.phases.typing.exceptions import NotIteratorError
raise NotIteratorError(iter_type)
return next_type
def is_builtin(x, feature):
return isinstance(x, BuiltinFeature) and x.val == feature
\ No newline at end of file
import ast
import enum
from dataclasses import dataclass
from transpiler.utils import highlight
from transpiler.exceptions import CompileError
from transpiler.phases.typing.types import TypeVariable, BaseType, TypeOperator
@dataclass
class UnresolvedTypeVariableError(CompileError):
variable: TypeVariable
def __str__(self) -> str:
return f"Unresolved type variable: {self.variable}"
def detail(self, last_node: ast.AST = None) -> str:
if isinstance(last_node, (ast.Import, ast.ImportFrom)):
return f"""
This indicates the compiler was unable to infer the type of a function in a module.
Currently, Typon cannot determine the type of Python functions imported from other modules, except
for the standard library.
As such, you need to give enough information to the compiler to infer the type of the function.
For example:
↓↓↓ this tells the compiler that {highlight('math.factorial')} returns an {highlight('int')}
{highlight('res: int = math.factorial(5)')}"""
return f"""
This generally indicates the compiler was unable to infer the type of a variable or expression.
A common fix is to add a type annotation to the variable or function.
For example:
↓↓↓ this tells the compiler that {highlight('x')} is an {highlight('int')}
{highlight('def f(x: int):')}
"""
@dataclass
class RecursiveTypeUnificationError(CompileError):
needle: BaseType
haystack: BaseType
def __str__(self) -> str:
return f"Recursive type unification: {highlight(self.needle)} and {highlight(self.haystack)}"
def detail(self, last_node: ast.AST = None) -> str:
return f"""
This generally indicates a recursive type definition. Such types are not currently supported.
For example:
{highlight('T = tuple[T]')}
In the current case, {highlight(self.haystack)} contains type {highlight(self.needle)}, but an attempt was made to
unify them.
"""
class TypeMismatchKind(enum.Enum):
NO_COMMON_PARENT = enum.auto()
DIFFERENT_TYPE = enum.auto()
@dataclass
class TypeMismatchError(CompileError):
expected: BaseType
got: BaseType
reason: TypeMismatchKind
def __str__(self) -> str:
return f"Type mismatch: expected {highlight(self.expected)}, got {highlight(self.got)}"
def detail(self, last_node: ast.AST = None) -> str:
return f"""
This generally indicates a type error.
For example:
{highlight('def f(x: int): ...')}
{highlight('f("hello")')}
In the current case, the compiler expected an expression of type {highlight(self.expected)}, but instead got
an expression of type {highlight(self.got)}.
"""
@dataclass
class ArgumentCountMismatchError(CompileError):
func: TypeOperator
arguments: TypeOperator
def __str__(self) -> str:
fcount = str(len(self.func.args))
if self.func.variadic:
fcount = f"at least {fcount}"
return f"Argument count mismatch: expected {fcount}, got {len(self.arguments.args)}"
def detail(self, last_node: ast.AST = None) -> str:
return f"""
This indicates missing or extraneous arguments in a function call or type instantiation.
The called or instantiated signature was {highlight(self.func)}.
Other examples:
{highlight('def f(x: int): ...')}
{highlight('f(1, 2)')}
Here, the function {highlight('f')} expects one argument, but was called with two.
{highlight('x: list[int, str]')}
Here, the type {highlight('list')} expects one argument, but was instantiated with two.
"""
@dataclass
class ProtocolMismatchError(CompileError):
value: BaseType
protocol: BaseType
reason: Exception | str
def __str__(self) -> str:
return f"Protocol mismatch: {str(self.value)} does not implement {str(self.protocol)}"
def detail(self, last_node: ast.AST = None) -> str:
return f"""
This generally indicates a type error.
For example:
{highlight('def f(x: Iterable[int]): ...')}
{highlight('f("hello")')}
In the current case, the compiler expected an expression whose type implements {highlight(self.protocol)}, but
instead got an expression of type {highlight(self.value)}.
"""
@dataclass
class NotCallableError(CompileError):
value: BaseType
def __str__(self) -> str:
return f"Trying to call a non-function type: {highlight(self.value)}"
def detail(self, last_node: ast.AST = None) -> str:
return f"""
This indicates that an attempt was made to call an object that is not a function.
For example:
{highlight('x = 1')}
{highlight('x()')}
"""
@dataclass
class MissingAttributeError(CompileError):
value: BaseType
attribute: str
def __str__(self) -> str:
return f"Missing attribute: {highlight(self.value)} has no attribute {highlight(self.attribute)}"
def detail(self, last_node: ast.AST = None) -> str:
return f"""
This indicates that an attempt was made to access an attribute that does not exist.
For example:
{highlight('x = 1')}
{highlight('print(x.y)')}
"""
@dataclass
class UnknownNameError(CompileError):
name: str
def __str__(self) -> str:
return f"Unknown name: {highlight(self.name)}"
def detail(self, last_node: ast.AST = None) -> str:
return f"""
This indicates that an attempt was made to access a name that does not exist.
For example:
{highlight('print(abcd)')}
{highlight('import foobar')}
"""
@dataclass
class UnknownModuleMemberError(CompileError):
module: str
name: str
def __str__(self) -> str:
return f"Unknown module member: Module {highlight(self.module)} does not contain {highlight(self.name)}"
def detail(self, last_node: ast.AST = None) -> str:
return f"""
This indicates that an attempt was made to import
For example:
{highlight('from math import abcd')}
"""
@dataclass
class InvalidUnpackCountError(CompileError):
value: BaseType
count: int
def __str__(self) -> str:
return f"Invalid unpack: {highlight(self.value)} cannot be unpacked into {self.count} variables"
def detail(self, last_node: ast.AST = None) -> str:
return f"""
This indicates that an attempt was made to unpack a value that cannot be unpacked into the given number of
variables.
For example:
{highlight('a, b, c = 1, 2')}
"""
@dataclass
class InvalidUnpackError(CompileError):
value: BaseType
def __str__(self) -> str:
return f"Invalid unpack: {highlight(self.value)} cannot be unpacked"
def detail(self, last_node: ast.AST = None) -> str:
return f"""
This indicates that an attempt was made to unpack a value that cannot be unpacked.
For example:
{highlight('a, b, c = 1')}
Moreover, currently typon only supports unpacking tuples.
"""
@dataclass
class NotIterableError(CompileError):
value: BaseType
def __str__(self) -> str:
return f"Not iterable: {highlight(self.value)} is not iterable"
def detail(self, last_node: ast.AST = None) -> str:
return f"""
This indicates that an attempt was made to iterate over a value that is not iterable.
For example:
{highlight('for x in 1: ...')}
Iterable types must implement the Python {highlight('Iterable')} protocol, which requires the presence of a
{highlight('__iter__')} method.
"""
@dataclass
class NotIteratorError(CompileError):
value: BaseType
def __str__(self) -> str:
return f"Not iterator: {highlight(self.value)} is not an iterator"
def detail(self, last_node: ast.AST = None) -> str:
return f"""
This indicates that an attempt was made to iterate over a value that is not an iterator.
For example:
{highlight('x = next(5)')}
Iterator types must implement the Python {highlight('Iterator')} protocol, which requires the presence of a
{highlight('__next__')} method.
"""
@dataclass
class OutsideFunctionError(CompileError):
def __str__(self) -> str:
return f"{highlight('return')} and {highlight('nonlocal')} cannot be used outside of a function"
def detail(self, last_node: ast.AST = None) -> str:
return ""
@dataclass
class OutsideLoopError(CompileError):
def __str__(self) -> str:
return f"{highlight('break')} and {highlight('continue')} can only be used inside a loop"
def detail(self, last_node: ast.AST = None) -> str:
return ""
@dataclass
class MissingReturnError(CompileError):
node: ast.FunctionDef
def __str__(self) -> str:
return f"Missing return: not all code paths in {highlight(self.node)} return"
def detail(self, last_node: ast.AST = None) -> str:
return f"""
This indicates that a function is missing a {highlight('return')} statement in one or more of its code paths.
For example:
{highlight('def f(x: int):')}
{highlight(' if x > 0:')}
{highlight(' return 1')}
{highlight(' # if x <= 0, the function returns nothing')}
"""
@dataclass
class InconsistentMroError(CompileError):
bases: list[BaseType]
def __str__(self) -> str:
return f"Cannot create a cnossitent method resolution order (MRO) for bases {'\n'.join(map(highlight, self.bases))}"
def detail(self, last_node: ast.AST = None) -> str:
return f"""
This indicates that a class has an inconsistent method resolution order (MRO).
For example:
{highlight('class A: pass')}
{highlight('class B(A): pass')}
{highlight('class C(B, A): pass')}
"""
\ No newline at end of file
import abc
import ast
import inspect
from typing import List
from transpiler.phases.typing import ScopeKind, VarDecl, VarKind
from transpiler.phases.typing.common import ScoperVisitor, get_iter, get_next, is_builtin
from transpiler.phases.typing.types import BaseType, TupleType, TY_STR, TY_BOOL, TY_INT, \
TY_COMPLEX, TY_NONE, FunctionType, PyList, TypeVariable, PySet, TypeType, PyDict, Promise, PromiseKind, UserType, \
TY_SLICE, TY_FLOAT, RuntimeValue, BuiltinFeature
from transpiler.utils import linenodata
DUNDER = {
ast.Eq: "eq",
ast.NotEq: "ne",
ast.Mult: "mul",
ast.Add: "add",
ast.Sub: "sub",
ast.Div: "truediv",
ast.FloorDiv: "floordiv",
ast.Mod: "mod",
ast.Lt: "lt",
ast.Gt: "gt",
ast.GtE: "ge",
ast.LtE: "le",
ast.LShift: "lshift",
ast.RShift: "rshift",
ast.BitXor: "xor",
ast.BitOr: "or",
ast.BitAnd: "and",
ast.USub: "neg",
ast.UAdd: "pos",
ast.Invert: "invert",
ast.In: "contains",
}
class ScoperExprVisitor(ScoperVisitor):
def visit(self, node) -> BaseType:
if existing := getattr(node, "type", None):
return existing.resolve()
__TB_SKIP__ = True
res = super().visit(node)
if not res:
__TB_SKIP__ = False
raise NotImplementedError(f"`{ast.unparse(node)}` {type(node)}")
res = res.resolve()
if True or not hasattr(res, "from_node"):
res.from_node = node
node.type = res
return res
def visit_Tuple(self, node: ast.Tuple) -> BaseType:
return TupleType(*[self.visit(e) for e in node.elts])
def visit_Slice(self, node: ast.Slice) -> BaseType:
for n in ("lower", "upper", "step"):
if arg := getattr(node, n):
self.visit(arg).unify(TY_INT)
return TY_SLICE
def visit_Yield(self, node: ast.Yield) -> BaseType:
ytype = self.visit(node.value)
ftype = self.scope.function.obj_type.return_type
assert isinstance(ftype, Promise)
assert ftype.kind == PromiseKind.TASK
ftype.kind = PromiseKind.GENERATOR
ftype.return_type.unify(ytype)
self.scope.function.has_yield = True
return TY_NONE
def visit_Constant(self, node: ast.Constant) -> BaseType:
if isinstance(node.value, str):
return TY_STR
elif isinstance(node.value, bool):
return TY_BOOL
elif isinstance(node.value, int):
return TY_INT
elif isinstance(node.value, complex):
return TY_COMPLEX
elif isinstance(node.value, float):
return TY_FLOAT
elif node.value is None:
return TY_NONE
else:
raise NotImplementedError(node, type(node))
def visit_Name(self, node: ast.Name) -> BaseType:
obj = self.scope.get(node.id)
if not obj:
from transpiler.phases.typing.exceptions import UnknownNameError
raise UnknownNameError(node.id)
ty = obj.type.resolve()
if isinstance(ty, TypeType) and isinstance(ty.type_object, TypeVariable):
raise NameError(f"Use of type variable") # todo: when does this happen exactly?
if getattr(ty, "is_python_func", False):
ty.python_func_used = True
return ty
def visit_BoolOp(self, node: ast.BoolOp) -> BaseType:
for value in node.values:
self.visit(value)
return TY_BOOL
def visit_Call(self, node: ast.Call) -> BaseType:
ftype = self.visit(node.func)
if is_builtin(ftype, "TypeVar"):
return TypeType(TypeVariable(*[ast.literal_eval(arg) for arg in node.args]))
if ftype.typevars:
ftype = ftype.gen_sub(None, {v.name: TypeVariable(v.name) for v in ftype.typevars})
from transpiler.exceptions import CompileError
rtype = self.visit_function_call(ftype, [self.visit(arg) for arg in node.args])
actual = rtype
node.is_await = False
if isinstance(actual, Promise) and actual.kind != PromiseKind.GENERATOR:
node.is_await = True
actual = actual.return_type.resolve()
if self.scope.function and isinstance(actual, Promise) and actual.kind == PromiseKind.FORKED \
and isinstance(fty := self.scope.function.obj_type.return_type, Promise):
fty.kind = PromiseKind.JOIN
return actual
def visit_function_call(self, ftype: BaseType, arguments: List[BaseType]):
if isinstance(ftype, TypeType):# and isinstance(ftype.type_object, UserType):
init: FunctionType = self.visit_getattr(ftype, "__init__").remove_self()
init.return_type = ftype.type_object
return self.visit_function_call(init, arguments)
if isinstance(ftype, FunctionType):
ret = ftype.return_type
elif isinstance(ftype, TypeVariable):
ret = TypeVariable()
else:
from transpiler.phases.typing.exceptions import NotCallableError
raise NotCallableError(ftype)
#is_generic = any(isinstance(arg, TypeVariable) for arg in ftype.to_list())
equivalent = FunctionType(arguments, ret)
equivalent.is_intermediary = True
ftype.unify(equivalent)
return equivalent.return_type
def visit_Lambda(self, node: ast.Lambda) -> BaseType:
argtypes = [TypeVariable() for _ in node.args.args]
rtype = TypeVariable()
ftype = FunctionType(argtypes, rtype)
scope = self.scope.child(ScopeKind.FUNCTION)
scope.obj_type = ftype
scope.function = scope
node.inner_scope = scope
node.body.scope = scope
for arg, ty in zip(node.args.args, argtypes):
scope.vars[arg.arg] = VarDecl(VarKind.LOCAL, ty)
decls = {}
visitor = ScoperExprVisitor(scope, decls)
rtype.unify(visitor.visit(node.body))
node.body.decls = decls
return ftype
# def visit_BinOp(self, node: ast.BinOp) -> BaseType:
# left, right = map(self.visit, (node.left, node.right))
# return self.make_dunder([left, right], DUNDER[type(node.op)])
# def visit_Compare(self, node: ast.Compare) -> BaseType:
# left, right = map(self.visit, (node.left, node.comparators[0]))
# op = node.ops[0]
# if type(op) == ast.In:
# left, right = right, left
# return self.make_dunder([left, right], DUNDER[type(op)])
def visit_Attribute(self, node: ast.Attribute) -> BaseType:
ltype = self.visit(node.value)
return self.visit_getattr(ltype, node.attr)
def visit_getattr(self, ltype: BaseType, name: str) -> BaseType:
bound = True
if isinstance(ltype, TypeType):
# if mdecl := ltype.static_members.get(name):
# attr = mdecl.type
# if getattr(attr, "is_python_func", False):
# attr.python_func_used = True
# return attr
ltype = ltype.type_object
bound = False
if isinstance(ltype, abc.ABCMeta):
ctor = ltype.__init__
args = list(inspect.signature(ctor).parameters.values())[1:]
if not all(arg.annotation == BaseType for arg in args):
raise NotImplementedError("I don't know how to handle this type")
ltype = ltype(*(TypeVariable() for _ in args))
# if mdecl := ltype.members.get(name):
# attr = mdecl.type
# if getattr(attr, "is_python_func", False):
# attr.python_func_used = True
# return attr
# if meth := ltype.methods.get(name):
# meth = meth.gen_sub(ltype, {})
# if bound:
# return meth.remove_self()
# else:
# return meth
if field := ltype.fields.get(name):
ty = field.type.resolve()
if getattr(ty, "is_python_func", False):
ty.python_func_used = True
if isinstance(ty, FunctionType):
ty = ty.gen_sub(ltype, {})
if bound and field.in_class_def and type(field.val) != RuntimeValue:
return ty.remove_self()
return ty
from transpiler.phases.typing.exceptions import MissingAttributeError
parents = ltype.iter_hierarchy_recursive()
next(parents)
for p in parents:
try:
return self.visit_getattr(p, name)
except MissingAttributeError as e:
pass
# class MemberProtocol(TypeOperator):
# pass
raise MissingAttributeError(ltype, name)
def visit_List(self, node: ast.List) -> BaseType:
if not node.elts:
return PyList(TypeVariable())
elems = [self.visit(e) for e in node.elts]
first, *rest = elems
for e in rest:
try:
first.unify(e)
except:
raise NotImplementedError(f"List with different types not handled yet: {', '.join(map(str, elems))}")
return PyList(elems[0])
def visit_Set(self, node: ast.Set) -> BaseType:
if not node.elts:
return PySet(TypeVariable())
elems = [self.visit(e) for e in node.elts]
if len(set(elems)) != 1:
raise NotImplementedError("Set with different types not handled yet")
return PySet(elems[0])
def visit_Dict(self, node: ast.Dict) -> BaseType:
if not node.keys:
return PyDict(TypeVariable(), TypeVariable())
keys = [self.visit(e) for e in node.keys]
values = [self.visit(e) for e in node.values]
if len(set(keys)) != 1 or len(set(values)) != 1:
raise NotImplementedError(f"Dict with different types not handled yet in `{ast.unparse(node)}`")
return PyDict(keys[0], values[0])
def visit_Subscript(self, node: ast.Subscript) -> BaseType:
left = self.visit(node.value)
args = node.slice if type(node.slice) == tuple else [node.slice]
args = [self.visit(e) for e in args]
if isinstance(left, TypeType) and isinstance(left.type_object, abc.ABCMeta):
# generic
return TypeType(left.type_object(*[arg.type_object if isinstance(arg, TypeType) else arg for arg in args]))
pass
return self.make_dunder([left, *args], "getitem")
def visit_UnaryOp(self, node: ast.UnaryOp) -> BaseType:
val = self.visit(node.operand)
if isinstance(node.op, ast.Not):
return TY_BOOL
return self.make_dunder([val], DUNDER[type(node.op)])
def visit_IfExp(self, node: ast.IfExp) -> BaseType:
self.visit(node.test)
then = self.visit(node.body)
else_ = self.visit(node.orelse)
if then != else_:
raise NotImplementedError("IfExp with different types not handled yet")
return then
def make_dunder(self, args: List[BaseType], name: str) -> BaseType:
return self.visit_function_call(
self.visit_getattr(TypeType(args[0]), f"__{name}__"),
args
)
def visit_ListComp(self, node: ast.ListComp) -> BaseType:
if len(node.generators) != 1:
raise NotImplementedError("Multiple generators not handled yet")
gen: ast.comprehension = node.generators[0]
iter_type = get_iter(self.visit(gen.iter))
node.input_item_type = get_next(iter_type)
virt_scope = self.scope.child(ScopeKind.FUNCTION_INNER)
from transpiler import ScoperBlockVisitor
visitor = ScoperBlockVisitor(virt_scope)
visitor.visit_assign_target(gen.target, node.input_item_type)
node.item_type = visitor.expr().visit(node.elt)
for if_ in gen.ifs:
visitor.expr().visit(if_)
gen.ifs_node = ast.BoolOp(ast.And(), gen.ifs, **linenodata(node))
return PyList(node.item_type)
\ No newline at end of file
import ast
from dataclasses import field, dataclass
from enum import Enum
from typing import Optional, Dict, List, Any
from transpiler.phases.typing.types import BaseType, RuntimeValue
class VarKind(Enum):
"""Kind of variable."""
LOCAL = 1
"""`xxx = ...`"""
GLOBAL = 2
"""`global xxx"""
NONLOCAL = 3
"""`nonlocal xxx`"""
SELF = 4
OUTER_DECL = 5
MODULE = 6
class VarType:
pass
@dataclass
class VarDecl:
kind: VarKind
type: BaseType
val: Any = RuntimeValue()
class ScopeKind(Enum):
GLOBAL = 1
"""Global (module) scope"""
FUNCTION = 2
"""Function scope"""
FUNCTION_INNER = 3
"""Block (if, for, ...) scope inside a function"""
CLASS = 4
"""Class scope"""
@dataclass
class Scope:
parent: Optional["Scope"] = None
kind: ScopeKind = ScopeKind.GLOBAL
function: Optional["Scope"] = None
global_scope: Optional["Scope"] = None
vars: Dict[str, VarDecl] = field(default_factory=dict)
children: List["Scope"] = field(default_factory=list)
obj_type: Optional[BaseType] = None
diverges: bool = False
class_: Optional["Scope"] = None
is_loop: Optional[ast.For | ast.While] = None
@staticmethod
def make_global():
res = Scope()
res.global_scope = res
return res
def is_in_loop(self) -> Optional[ast.For | ast.While]:
if self.is_loop:
return self.is_loop
if self.parent is not None and self.kind != ScopeKind.FUNCTION:
return self.parent.is_in_loop()
return None
def child(self, kind: ScopeKind):
res = Scope(self, kind, self.function, self.global_scope)
if kind == ScopeKind.GLOBAL:
res.global_scope = res
self.children.append(res)
return res
def declare_local(self, name: str, type: BaseType):
"""Declares a local variable"""
self.vars[name] = VarDecl(VarKind.LOCAL, type)
def get(self, name: str, kind: VarKind | set[VarKind] = VarKind.LOCAL, restrict_function: bool = False) -> Optional[VarDecl]:
"""
Gets the variable declaration of a variable in the current scope or any parent scope.
"""
if type(kind) is VarKind:
kind = {kind}
if (res := self.vars.get(name)) and res.kind in kind:
if res.kind == VarKind.GLOBAL:
return self.global_scope.get(name, kind)
elif res.kind == VarKind.NONLOCAL:
return self.function.parent.get(name, VarKind.LOCAL, True)
return res
if self.parent is not None and not (self.kind == ScopeKind.FUNCTION and restrict_function):
return self.parent.get(name, kind, restrict_function)
return None
import ast
import dataclasses
from abc import ABCMeta
from dataclasses import dataclass, field
from typing import Optional, List, Dict
from logging import debug
from transpiler.phases.typing.annotations import TypeAnnotationVisitor
from transpiler.phases.typing.common import PRELUDE, is_builtin
from transpiler.phases.typing.expr import ScoperExprVisitor
from transpiler.phases.typing.scope import Scope, VarDecl, VarKind, ScopeKind
from transpiler.phases.typing.types import BaseType, TypeOperator, FunctionType, TY_VARARG, TypeType, TypeVariable, \
MemberDef, BuiltinFeature
from transpiler.phases.utils import NodeVisitorSeq
@dataclass
class StdlibVisitor(NodeVisitorSeq):
scope: Scope = field(default_factory=lambda: PRELUDE)
cur_class: Optional[BaseType] = None
typevars: Dict[str, BaseType] = field(default_factory=dict)
def expr(self) -> ScoperExprVisitor:
return ScoperExprVisitor(self.scope)
def visit_Module(self, node: ast.Module):
for stmt in node.body:
self.visit(stmt)
def visit_Assign(self, node: ast.Assign):
self.scope.vars[node.targets[0].id] = VarDecl(VarKind.LOCAL, self.visit(node.value))
def visit_AnnAssign(self, node: ast.AnnAssign):
ty = self.anno().visit(node.annotation)
if self.cur_class:
assert isinstance(self.cur_class, TypeType)
if isinstance(self.cur_class.type_object, ABCMeta):
raise NotImplementedError
else:
self.cur_class.type_object.fields[node.target.id] = MemberDef(ty.gen_sub(self.cur_class.type_object, self.typevars))
self.scope.vars[node.target.id] = VarDecl(VarKind.LOCAL, ty)
def visit_ImportFrom(self, node: ast.ImportFrom):
pass
def visit_Import(self, node: ast.Import):
pass
def visit_ClassDef(self, node: ast.ClassDef):
if existing := self.scope.get(node.name):
ty = existing.type
else:
class BuiltinClassType(TypeOperator):
def __init__(self, *args):
super().__init__(args, node.name, is_reference=True)
ty = TypeType(BuiltinClassType)
self.scope.vars[node.name] = VarDecl(VarKind.LOCAL, ty)
typevars = []
for b in node.bases:
if isinstance(b, ast.Subscript):
if isinstance(b.slice, ast.Name):
sliceval = [b.slice.id]
elif isinstance(b.slice, ast.Tuple):
sliceval = [n.id for n in b.slice.elts]
if isinstance(b.value, ast.Name) and b.value.id == "Generic":
typevars = sliceval
elif isinstance(b.value, ast.Name) and b.value.id == "Protocol":
typevars = sliceval
ty.type_object.is_protocol_gen = True
else:
idxs = [typevars.index(v) for v in sliceval]
parent = self.visit(b.value)
assert isinstance(parent, TypeType)
assert isinstance(ty.type_object, ABCMeta)
ty.type_object.gen_parents.append(lambda selfvars: parent.type_object(*[selfvars[i] for i in idxs]))
else:
if isinstance(b, ast.Name) and b.id == "Protocol":
ty.type_object.is_protocol_gen = True
else:
parent = self.visit(b)
assert isinstance(parent, TypeType)
if isinstance(ty.type_object, ABCMeta):
ty.type_object.gen_parents.append(parent.type_object)
else:
ty.type_object.parents.append(parent.type_object)
if not typevars and not existing:
ty.type_object = ty.type_object()
cl_scope = self.scope.child(ScopeKind.CLASS)
visitor = StdlibVisitor(cl_scope, ty)
for var in typevars:
visitor.typevars[var] = TypeType(TypeVariable(var))
for stmt in node.body:
visitor.visit(stmt)
def visit_Pass(self, node: ast.Pass):
pass
def visit_FunctionDef(self, node: ast.FunctionDef):
tc = node.type_comment # todo : lire les commetnaries de type pour les fonctions génériques sinon trouver autre chose
arg_visitor = TypeAnnotationVisitor(self.scope.child(ScopeKind.FUNCTION), self.cur_class)
arg_types = [arg_visitor.visit(arg.annotation or arg.arg) for arg in node.args.args]
ret_type = arg_visitor.visit(node.returns)
ty = FunctionType(arg_types, ret_type)
ty.typevars = arg_visitor.typevars
if node.args.vararg:
ty.variadic = True
ty.optional_at = 1 + len(node.args.args) - len(node.args.defaults)
if self.cur_class:
ty.is_method = True
assert isinstance(self.cur_class, TypeType)
if isinstance(self.cur_class.type_object, ABCMeta):
self.cur_class.type_object.gen_methods[node.name] = lambda t: ty.gen_sub(t, self.typevars)
else:
self.cur_class.type_object.fields[node.name] = MemberDef(ty.gen_sub(self.cur_class.type_object, self.typevars), ())
self.scope.vars[node.name] = VarDecl(VarKind.LOCAL, ty)
def visit_Assert(self, node: ast.Assert):
if isinstance(node.test, ast.UnaryOp) and isinstance(node.test.op, ast.Not):
oper = node.test.operand
try:
res = self.expr().visit(oper)
except:
debug(f"Type of {ast.unparse(oper)} := INVALID")
else:
raise AssertionError(f"Assertion should fail, got {res} for {ast.unparse(oper)}")
else:
debug(f"Type of {ast.unparse(node.test)} := {self.expr().visit(node.test)}")
def visit_Call(self, node: ast.Call) -> BaseType:
ty_op = self.visit(node.func)
if is_builtin(ty_op, "TypeVar"):
return TypeType(TypeVariable(*[ast.literal_eval(arg) for arg in node.args]))
if isinstance(ty_op, TypeType):
return TypeType(ty_op.type_object(*[ast.literal_eval(arg) for arg in node.args]))
raise NotImplementedError(ast.unparse(node))
def anno(self) -> "TypeAnnotationVisitor":
return TypeAnnotationVisitor(self.scope, self.cur_class)
def visit_str(self, node: str) -> BaseType:
if existing := self.scope.get(node):
return existing.type
from transpiler.phases.typing.exceptions import UnknownNameError
raise UnknownNameError(node)
def visit_Name(self, node: ast.Name) -> BaseType:
if node.id == "TypeVar":
return BuiltinFeature("TypeVar")
return self.visit_str(node.id)
\ No newline at end of file
import typing
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Dict, Optional, Callable
def get_default_parents():
if obj := globals().get("TY_OBJECT"):
return [obj]
return []
class RuntimeValue:
pass
@dataclass
class MemberDef:
type: "BaseType"
val: typing.Any = RuntimeValue()
in_class_def: bool = True
@dataclass(eq=False)
class BaseType(ABC):
def resolve(self) -> "BaseType":
return self
cur_var = 0
def next_var_id():
global cur_var
cur_var += 1
return cur_var
@dataclass(eq=False)
class ConcreteType(BaseType):
"""
A concrete type is the type of a concrete value.
It has fields and a list of parent concrete types.
Examples: int, str, list[int]
"""
@dataclass(eq=False)
class TypeVariable(ConcreteType):
name: str = field(default_factory=lambda: next_var_id())
resolved: Optional[ConcreteType] = None
def resolve(self) -> ConcreteType:
if self.resolved is None:
return self
return self.resolved.resolve()
def __str__(self):
if self.resolved is None:
# return f"TypeVar[\"{self.name}\"]"
return f"_{self.name}"
return str(self.resolved)
def __eq__(self, other):
if not isinstance(other, BaseType):
return False
if self.resolved is None:
return self == other
return self.resolved == other.resolve()
@dataclass(eq=False)
class ResolvedConcreteType(ConcreteType):
"""
A concrete type is the type of a concrete value.
It has fields and a list of parent concrete types.
Examples: int, str, list[int]
"""
fields: Dict[str, "MemberDef"] = field(default_factory=dict, init=False)
parents: list["ResolvedConcreteType"] = field(default_factory=lambda: [TY_OBJECT], init=False)
def get_mro(self):
"""
Performs linearization according to the MRO spec.
https://www.python.org/download/releases/2.3/mro/
"""
def merge(*lists):
lists = [l for l in lists if len(l) > 0]
for i, l in enumerate(lists):
first = l[0]
for j, l2 in enumerate(lists):
if j == i:
continue
if first in l2:
break
else:
return [first] + merge(*[x[1:] for x in lists if x[0] != first])
# unable to find a next element
from transpiler.phases.typing.exceptions import InconsistentMroError
raise InconsistentMroError(self.parents)
return [self] + merge(*[p.get_mro() for p in self.parents], self.parents)
def inherits(self, parent: BaseType):
return self == parent or any(p.inherits(parent) for p in self.parents)
@dataclass(eq=False, init=False)
class GenericInstanceType(ResolvedConcreteType):
"""
An instance of a generic type.
Examples: list[int], dict[str, object], Callable[[int, int], int]
"""
generic_parent: "GenericType"
generic_args: list[ConcreteType]
def __init__(self):
super().__init__()
def inherits(self, parent: BaseType):
return self.generic_parent == parent or super().inherits(parent)
def __eq__(self, other):
if isinstance(other, GenericInstanceType):
return self.generic_parent == other.generic_parent and self.generic_args == other.generic_args
return False
def __str__(self):
return f"{self.generic_parent}[{', '.join(map(str, self.generic_args))}]"
@dataclass
class GenericConstraint:
left: ResolvedConcreteType
right: ResolvedConcreteType
@dataclass(eq=False, init=False)
class GenericType(BaseType):
parameters: list[str]
def constraints(self, args: list[ConcreteType]) -> list[GenericConstraint]:
return []
@abstractmethod
def _instantiate(self, args: list[ConcreteType]) -> GenericInstanceType:
raise NotImplementedError()
def instantiate(self, args: list[ConcreteType]) -> GenericInstanceType:
res = self._instantiate(args)
res.generic_args = args
res.generic_parent = self
return res
@dataclass(eq=False, init=False)
class BuiltinGenericType(GenericType):
constraints_: Callable[[list[ConcreteType]], list[GenericConstraint]]
instantiate_: Callable[[list[ConcreteType]], GenericInstanceType]
def constraints(self, args: list[ConcreteType]) -> list[GenericConstraint]:
return self.constraints_(args)
def _instantiate(self, args: list[ConcreteType]) -> GenericInstanceType:
return self.instantiate_(args)
def create_builtin_type(name: str):
class CreatedType(BuiltinGenericType):
def __str__(self):
return name
res = CreatedType()
return res
TY_OBJECT = None
TY_OBJECT = create_builtin_type("object")
TY_OBJECT.parents = []
TY_BOOL = create_builtin_type("bool")
TY_INT = create_builtin_type("int")
TY_FLOAT = create_builtin_type("float")
TY_STR = create_builtin_type("str")
TY_BYTES = create_builtin_type("bytes")
TY_COMPLEX = create_builtin_type("complex")
TY_NONE = create_builtin_type("NoneType")
def unimpl(*args, **kwargs):
raise NotImplementedError()
def create_builtin_generic_type(name: str):
class CreatedType(BuiltinGenericType):
def __str__(self):
return name
res = CreatedType()
return res
TY_LIST = create_builtin_generic_type("list")
TY_SET = create_builtin_generic_type("set")
TY_DICT = create_builtin_generic_type("dict")
TY_TUPLE = create_builtin_generic_type("tuple")
@dataclass(unsafe_hash=False)
class TypeListType(ConcreteType):
"""
Special type used to represent a list of types.
Used in function types: Callable[[int, int], int]
"""
contents: list[ConcreteType]
def __str__(self):
return f"[{', '.join(map(str, self.contents))}]"
@dataclass(eq=False)
class CallableInstanceType(GenericInstanceType):
parameters: list[ConcreteType]
return_type: ConcreteType
class CallableType(GenericType):
def __str__(self):
return "Callable"
def _instantiate(self, args: list[ConcreteType]) -> GenericInstanceType:
match args:
case [TypeListType([*args]), ret]:
return CallableInstanceType(args, ret)
case _:
raise ValueError
TY_CALLABLE = CallableType()
@dataclass(eq=False)
class ClassTypeType(GenericInstanceType):
inner_type: BaseType
class ClassType(GenericType):
def __str__(self):
return "Type"
def _instantiate(self, args: list[ConcreteType]) -> GenericInstanceType:
return ClassTypeType(*args)
TY_TYPE = ClassType()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment