feat(checker): adapt typers to members and extension type
This commit is contained in:
@@ -8,6 +8,7 @@ from midas.checker.reporter import FileReporter, Reporter
|
|||||||
from midas.checker.types import (
|
from midas.checker.types import (
|
||||||
AliasType,
|
AliasType,
|
||||||
ComplexType,
|
ComplexType,
|
||||||
|
ExtensionType,
|
||||||
Function,
|
Function,
|
||||||
GenericType,
|
GenericType,
|
||||||
Type,
|
Type,
|
||||||
@@ -76,7 +77,7 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[Type
|
|||||||
self.types.define_type(name, type)
|
self.types.define_type(name, type)
|
||||||
self._local_variables.clear()
|
self._local_variables.clear()
|
||||||
|
|
||||||
def visit_property_stmt(self, stmt: m.PropertyStmt) -> None: ...
|
def visit_member_stmt(self, stmt: m.MemberStmt) -> None: ...
|
||||||
|
|
||||||
def visit_extend_stmt(self, stmt: m.ExtendStmt) -> None:
|
def visit_extend_stmt(self, stmt: m.ExtendStmt) -> None:
|
||||||
self._resolve_type_params(stmt.params)
|
self._resolve_type_params(stmt.params)
|
||||||
@@ -126,16 +127,21 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[Type
|
|||||||
# TODO
|
# TODO
|
||||||
return UnknownType()
|
return UnknownType()
|
||||||
|
|
||||||
def visit_complex_type(self, type: m.ComplexType) -> Type:
|
def visit_complex_type(self, type: m.ComplexType) -> ComplexType:
|
||||||
return ComplexType(
|
return ComplexType(
|
||||||
properties={
|
members={
|
||||||
prop.name.lexeme: prop.type.accept(self) for prop in type.properties
|
member.name.lexeme: member.type.accept(self) for member in type.members
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def visit_extension_type(self, type: m.ExtensionType) -> Type:
|
||||||
|
return ExtensionType(
|
||||||
|
base=type.base.accept(self),
|
||||||
|
extension=self.visit_complex_type(type.extension),
|
||||||
|
)
|
||||||
|
|
||||||
def visit_function_type(self, type: m.FunctionType) -> Type:
|
def visit_function_type(self, type: m.FunctionType) -> Type:
|
||||||
return Function(
|
return Function(
|
||||||
name="<anonymous>",
|
|
||||||
pos_args=[
|
pos_args=[
|
||||||
Function.Argument(
|
Function.Argument(
|
||||||
pos=i,
|
pos=i,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from midas.checker.reporter import FileReporter, Reporter
|
|||||||
from midas.checker.resolver import Resolver
|
from midas.checker.resolver import Resolver
|
||||||
from midas.checker.types import (
|
from midas.checker.types import (
|
||||||
ComplexType,
|
ComplexType,
|
||||||
|
ExtensionType,
|
||||||
Function,
|
Function,
|
||||||
Operation,
|
Operation,
|
||||||
Type,
|
Type,
|
||||||
@@ -192,7 +193,6 @@ class PythonTyper(
|
|||||||
returns_hint = stmt.returns.accept(self)
|
returns_hint = stmt.returns.accept(self)
|
||||||
# Early define to handle simple fully-typed recursion
|
# Early define to handle simple fully-typed recursion
|
||||||
inside_function: Function = Function(
|
inside_function: Function = Function(
|
||||||
name=stmt.name,
|
|
||||||
pos_args=pos_args,
|
pos_args=pos_args,
|
||||||
args=args,
|
args=args,
|
||||||
kw_args=kw_args,
|
kw_args=kw_args,
|
||||||
@@ -227,7 +227,6 @@ class PythonTyper(
|
|||||||
|
|
||||||
# TODO: handle *args and **kwargs sinks
|
# TODO: handle *args and **kwargs sinks
|
||||||
function: Function = Function(
|
function: Function = Function(
|
||||||
name=stmt.name,
|
|
||||||
pos_args=pos_args,
|
pos_args=pos_args,
|
||||||
args=args,
|
args=args,
|
||||||
kw_args=kw_args,
|
kw_args=kw_args,
|
||||||
@@ -250,8 +249,9 @@ class PythonTyper(
|
|||||||
case p.VariableExpr():
|
case p.VariableExpr():
|
||||||
self._assign_var(location, target, value_type)
|
self._assign_var(location, target, value_type)
|
||||||
|
|
||||||
case p.GetExpr():
|
case p.GetExpr(object=object, name=name):
|
||||||
self._assign_attr(location, target, value_type)
|
object_type: Type = self.type_of(object)
|
||||||
|
self._assign_attr(location, object_type, name, value_type)
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
if not isinstance(target, p.VariableExpr):
|
if not isinstance(target, p.VariableExpr):
|
||||||
@@ -276,32 +276,43 @@ class PythonTyper(
|
|||||||
f"Cannot assign {value_type} to variable '{name}' of type {var_type}",
|
f"Cannot assign {value_type} to variable '{name}' of type {var_type}",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _assign_attr(self, location: Location, target: p.GetExpr, value_type: Type):
|
def _assign_attr(
|
||||||
object: Type = self.type_of(target.object)
|
self, location: Location, object: Type, name: str, value_type: Type
|
||||||
|
):
|
||||||
|
# TODO: improve recursion to have better error messages
|
||||||
base_object: Type = unfold_type(object)
|
base_object: Type = unfold_type(object)
|
||||||
match base_object:
|
match base_object:
|
||||||
case ComplexType(properties=properties):
|
case ComplexType(members=members):
|
||||||
if target.name not in properties:
|
if name not in members:
|
||||||
|
self.reporter.error(location, f"Unknown member '{object}.{name}'")
|
||||||
|
return
|
||||||
|
|
||||||
|
member_type: Type = members[name]
|
||||||
|
if not self.is_subtype(value_type, member_type):
|
||||||
self.reporter.error(
|
self.reporter.error(
|
||||||
target.location, f"Unknown property '{object}.{target.name}'"
|
location,
|
||||||
|
f"Cannot assign {value_type} to member '{object}.{name}' of type {member_type}",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
prop_type: Type = properties[target.name]
|
case ExtensionType(base=base, extension=ComplexType(members=members)):
|
||||||
if not self.is_subtype(value_type, prop_type):
|
if name in members:
|
||||||
self.reporter.error(
|
member_type: Type = members[name]
|
||||||
location,
|
if not self.is_subtype(value_type, member_type):
|
||||||
f"Cannot assign {value_type} to property '{object}.{target.name}' of type {prop_type}",
|
self.reporter.error(
|
||||||
)
|
location,
|
||||||
return
|
f"Cannot assign {value_type} to member '{object}.{name}' of type {member_type}",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
return self._assign_attr(location, base, name, value_type)
|
||||||
|
|
||||||
case UnknownType():
|
case UnknownType():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
self.reporter.error(
|
self.reporter.error(
|
||||||
target.location,
|
location,
|
||||||
f"Cannot assign {value_type} to unknown property '{object}.{target.name}'",
|
f"Cannot assign {value_type} to unknown property '{object}.{name}'",
|
||||||
)
|
)
|
||||||
|
|
||||||
def visit_return_stmt(self, stmt: p.ReturnStmt) -> None:
|
def visit_return_stmt(self, stmt: p.ReturnStmt) -> None:
|
||||||
@@ -422,23 +433,37 @@ class PythonTyper(
|
|||||||
|
|
||||||
def visit_get_expr(self, expr: p.GetExpr) -> Type:
|
def visit_get_expr(self, expr: p.GetExpr) -> Type:
|
||||||
object: Type = self.type_of(expr.object)
|
object: Type = self.type_of(expr.object)
|
||||||
|
member: Optional[Type] = self._get_member(object, expr.name)
|
||||||
|
if member is None:
|
||||||
|
self.reporter.error(
|
||||||
|
expr.location, f"Unknown property '{expr.name}' on {object}"
|
||||||
|
)
|
||||||
|
return UnknownType()
|
||||||
|
self.logger.debug(f"Property '{expr.name}' on {object} has type {member}")
|
||||||
|
return member
|
||||||
|
|
||||||
|
def _get_member(self, object: Type, name: str) -> Optional[Type]:
|
||||||
base_object: Type = unfold_type(object)
|
base_object: Type = unfold_type(object)
|
||||||
match base_object:
|
match base_object:
|
||||||
case ComplexType(properties=properties):
|
case ComplexType(members=members):
|
||||||
if expr.name not in properties:
|
if name in members:
|
||||||
self.reporter.error(
|
return members[name]
|
||||||
expr.location, f"Unknown property '{expr.name} on {object}"
|
self.logger.debug(f"No property '{name}' in {base_object}")
|
||||||
)
|
return None
|
||||||
return UnknownType()
|
|
||||||
return properties[expr.name]
|
case ExtensionType(base=base, extension=ComplexType(members=members)):
|
||||||
|
if name in members:
|
||||||
|
return members[name]
|
||||||
|
self.logger.debug(
|
||||||
|
f"No property '{name}' on {base_object}, looking up in base"
|
||||||
|
)
|
||||||
|
return self._get_member(base, name)
|
||||||
|
|
||||||
case UnknownType():
|
case UnknownType():
|
||||||
return UnknownType()
|
return UnknownType()
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
self.reporter.error(
|
self.logger.debug(f"Can't get property on {base_object}")
|
||||||
expr.location, f"Cannot get property '{expr.name}' on {object}"
|
|
||||||
)
|
|
||||||
return UnknownType()
|
return UnknownType()
|
||||||
|
|
||||||
def visit_literal_expr(self, expr: p.LiteralExpr) -> Type:
|
def visit_literal_expr(self, expr: p.LiteralExpr) -> Type:
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class UnitType:
|
|||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class Function:
|
class Function:
|
||||||
name: str
|
|
||||||
pos_args: list[Argument]
|
pos_args: list[Argument]
|
||||||
args: list[Argument]
|
args: list[Argument]
|
||||||
kw_args: list[Argument]
|
kw_args: list[Argument]
|
||||||
@@ -56,7 +55,7 @@ class Function:
|
|||||||
args.append("*")
|
args.append("*")
|
||||||
args += list(map(str, self.kw_args))
|
args += list(map(str, self.kw_args))
|
||||||
|
|
||||||
return f"{self.name}({', '.join(args)}) -> {self.returns}"
|
return f"({', '.join(args)}) -> {self.returns}"
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class Argument:
|
class Argument:
|
||||||
@@ -72,13 +71,22 @@ class Function:
|
|||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class ComplexType:
|
class ComplexType:
|
||||||
properties: dict[str, Type]
|
members: dict[str, Type]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
props: list[str] = [f"{name}: {type}" for name, type in self.properties.items()]
|
props: list[str] = [f"{name}: {type}" for name, type in self.members.items()]
|
||||||
return f"{{{', '.join(props)}}}"
|
return f"{{{', '.join(props)}}}"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class ExtensionType:
|
||||||
|
base: Type
|
||||||
|
extension: ComplexType
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.base} & {self.extension}"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class Operation:
|
class Operation:
|
||||||
signature: CallSignature
|
signature: CallSignature
|
||||||
@@ -145,26 +153,35 @@ def substitute_typevars(type: Type, substitutions: dict[str, Type]) -> Type:
|
|||||||
return AliasType(name=name, type=substitute_typevars(type2, substitutions))
|
return AliasType(name=name, type=substitute_typevars(type2, substitutions))
|
||||||
|
|
||||||
case Function(
|
case Function(
|
||||||
name=name,
|
|
||||||
pos_args=pos_args,
|
pos_args=pos_args,
|
||||||
args=args,
|
args=args,
|
||||||
kw_args=kw_args,
|
kw_args=kw_args,
|
||||||
returns=returns,
|
returns=returns,
|
||||||
):
|
):
|
||||||
return Function(
|
return Function(
|
||||||
name=name,
|
|
||||||
pos_args=list(map(sub_argument, pos_args)),
|
pos_args=list(map(sub_argument, pos_args)),
|
||||||
args=list(map(sub_argument, args)),
|
args=list(map(sub_argument, args)),
|
||||||
kw_args=list(map(sub_argument, kw_args)),
|
kw_args=list(map(sub_argument, kw_args)),
|
||||||
returns=substitute_typevars(returns, substitutions),
|
returns=substitute_typevars(returns, substitutions),
|
||||||
)
|
)
|
||||||
|
|
||||||
case ComplexType(properties=properties):
|
case ComplexType(members=members):
|
||||||
properties2: dict[str, Type] = {
|
members2: dict[str, Type] = {
|
||||||
name: substitute_typevars(prop, substitutions)
|
name: substitute_typevars(prop, substitutions)
|
||||||
for name, prop in properties.items()
|
for name, prop in members.items()
|
||||||
}
|
}
|
||||||
return ComplexType(properties=properties2)
|
return ComplexType(members=members2)
|
||||||
|
|
||||||
|
case ExtensionType(base=base, extension=ComplexType(members=members)):
|
||||||
|
return ExtensionType(
|
||||||
|
base=substitute_typevars(base, substitutions),
|
||||||
|
extension=ComplexType(
|
||||||
|
members={
|
||||||
|
name: substitute_typevars(prop, substitutions)
|
||||||
|
for name, prop in members.items()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
case TypeVar(name=name):
|
case TypeVar(name=name):
|
||||||
if name in substitutions:
|
if name in substitutions:
|
||||||
@@ -193,6 +210,7 @@ Type = (
|
|||||||
| UnitType
|
| UnitType
|
||||||
| Function
|
| Function
|
||||||
| ComplexType
|
| ComplexType
|
||||||
|
| ExtensionType
|
||||||
| TypeVar
|
| TypeVar
|
||||||
| GenericType
|
| GenericType
|
||||||
| AppliedType
|
| AppliedType
|
||||||
|
|||||||
Reference in New Issue
Block a user