3 Commits
Author SHA1 Message Date
HEL e0179bc442 feat(checker): handle assignments to attributes 2026-06-07 17:50:56 +02:00
HEL e665d03533 fix: remove unused SetExpr 2026-06-07 17:48:31 +02:00
HEL b8cb2b4273 feat(checker): handle attribute getter 2026-06-07 15:07:24 +02:00
10 changed files with 112 additions and 70 deletions
@@ -0,0 +1,11 @@
type Meter = float
extend Meter {
op __add__(Meter) -> Meter
op __sub__(Meter) -> Meter
}
type Coordinate = {
x: Meter
y: Meter
}
@@ -0,0 +1,11 @@
# type: ignore
# ruff: disable [F821]
p1: Coordinate
p2: Coordinate
diff_x = p2.x - p1.x
diff_y = p2.y - p1.y
dist = diff_x + diff_y
p2.x += cast(Meter, 1)
-6
View File
@@ -128,12 +128,6 @@ class LogicalExpr:
right: Expr
class SetExpr:
object: Expr
name: str
value: Expr
class CastExpr:
type: MidasType
expr: Expr
-11
View File
@@ -602,17 +602,6 @@ class PythonAstPrinter(
with self._child_level(single=True):
expr.right.accept(self)
def visit_set_expr(self, expr: p.SetExpr) -> None:
self._write_line("SetExpr")
with self._child_level():
self._write_line("object")
with self._child_level(single=True):
expr.object.accept(self)
self._write_line(f"name: {expr.name}")
self._write_line("value", last=True)
with self._child_level(single=True):
expr.value.accept(self)
def visit_cast_expr(self, expr: p.CastExpr) -> None:
self._write_line("CastExpr")
with self._child_level():
-13
View File
@@ -214,9 +214,6 @@ class Expr(ABC):
@abstractmethod
def visit_logical_expr(self, expr: LogicalExpr) -> T: ...
@abstractmethod
def visit_set_expr(self, expr: SetExpr) -> T: ...
@abstractmethod
def visit_cast_expr(self, expr: CastExpr) -> T: ...
@@ -298,16 +295,6 @@ class LogicalExpr(Expr):
return visitor.visit_logical_expr(self)
@dataclass(frozen=True)
class SetExpr(Expr):
object: Expr
name: str
value: Expr
def accept(self, visitor: Expr.Visitor[T]) -> T:
return visitor.visit_set_expr(self)
@dataclass(frozen=True)
class CastExpr(Expr):
type: MidasType
+82 -18
View File
@@ -178,6 +178,13 @@ class Checker(
stmts: list[m.Stmt] = parser.parse()
self.ctx.resolve(stmts)
def unfold_type(self, type: Type) -> Type:
match type:
case AliasType(type=ref_type):
return self.unfold_type(ref_type)
case _:
return type
def is_subtype(self, type1: Type, type2: Type) -> bool:
"""Check whether `type1` is a subtype of `type2`
@@ -430,24 +437,64 @@ class Checker(
def visit_assign_stmt(self, stmt: p.AssignStmt) -> None:
value_type: Type = self.type_of(stmt.value)
for target in stmt.targets:
if not isinstance(target, p.VariableExpr):
self.logger.warning(f"Unsupported assignment to {target}")
self.warning(target.location, f"Unsupported assignment to {target}")
continue
name: str = target.name
var_type: Optional[Type] = self.look_up_variable(name, target)
self._assign(stmt.location, target, value_type)
if var_type is None:
self.env.define(name, value_type)
else:
# S <: T
# Γ, x: T v: S
# x = v
if not self.is_subtype(value_type, var_type):
def _assign(self, location: Location, target: p.Expr, value_type: Type):
match target:
case p.VariableExpr():
self._assign_var(location, target, value_type)
case p.GetExpr():
self._assign_attr(location, target, value_type)
case _:
if not isinstance(target, p.VariableExpr):
self.logger.warning(f"Unsupported assignment to {target}")
self.warning(target.location, f"Unsupported assignment to {target}")
def _assign_var(self, location: Location, target: p.VariableExpr, value_type: Type):
name: str = target.name
var_type: Optional[Type] = self.look_up_variable(name, target)
if var_type is None:
self.env.define(name, value_type)
else:
# S <: T
# Γ, x: T v: S
# x = v
if not self.is_subtype(value_type, var_type):
self.error(
location,
f"Cannot assign {value_type} to {name} of type {var_type}",
)
def _assign_attr(self, location: Location, target: p.GetExpr, value_type: Type):
object: Type = self.type_of(target.object)
base_object: Type = self.unfold_type(object)
match base_object:
case ComplexType(properties=properties):
if target.name not in properties:
self.error(
stmt.location,
f"Cannot assign {value_type} to {name} of type {var_type}",
target.location, f"Unknown property '{target.name} on {object}"
)
return
prop_type: Type = properties[target.name]
if not self.is_subtype(value_type, prop_type):
self.error(
location,
f"Cannot assign {value_type} to property '{target.name}' of type {prop_type} on {object}",
)
return
case UnknownType():
pass
case _:
self.error(
target.location,
f"Cannot assign {value_type} to unknown property '{target.name}' on {object}",
)
def visit_return_stmt(self, stmt: p.ReturnStmt) -> None:
type: Type = stmt.value.accept(self) if stmt.value is not None else UnitType()
@@ -562,7 +609,26 @@ class Checker(
)
return function.returns
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)
base_object: Type = self.unfold_type(object)
match base_object:
case ComplexType(properties=properties):
if expr.name not in properties:
self.error(
expr.location, f"Unknown property '{expr.name} on {object}"
)
return UnknownType()
return properties[expr.name]
case UnknownType():
return UnknownType()
case _:
self.error(
expr.location, f"Cannot get property '{expr.name}' on {object}"
)
return UnknownType()
def visit_literal_expr(self, expr: p.LiteralExpr) -> Type:
match expr.value:
@@ -596,8 +662,6 @@ class Checker(
)
return UnknownType()
def visit_set_expr(self, expr: p.SetExpr) -> Type: ...
def visit_cast_expr(self, expr: p.CastExpr) -> Type:
return expr.type.accept(self)
-2
View File
@@ -210,8 +210,6 @@ class PythonHighlighter(
def visit_logical_expr(self, expr: p.LogicalExpr) -> None: ...
def visit_set_expr(self, expr: p.SetExpr) -> None: ...
def visit_cast_expr(self, expr: p.CastExpr) -> None: ...
def visit_ternary_expr(self, expr: p.TernaryExpr) -> None: ...
+6 -4
View File
@@ -3,6 +3,7 @@ from typing import Optional
import midas.ast.midas as m
from midas.checker.types import (
AliasType,
ComplexType,
Operation,
Type,
UnknownType,
@@ -178,7 +179,8 @@ class MidasResolver(m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[T
return UnknownType()
def visit_complex_type(self, type: m.ComplexType) -> Type:
for prop in type.properties:
prop.accept(self)
# TODO
return UnknownType()
return ComplexType(
properties={
prop.name.lexeme: prop.type.accept(self) for prop in type.properties
}
)
+2 -7
View File
@@ -111,9 +111,8 @@ class Resolver(p.Stmt.Visitor[None], p.Expr.Visitor[None]):
self.resolve(stmt.value)
for target in stmt.targets:
match target:
case p.VariableExpr(name=name):
self.resolve_local(target, name)
# TODO: declare if not found
case p.VariableExpr() | p.GetExpr():
target.accept(self)
case _:
raise Exception(f"Unsupported assignment to {target}")
@@ -174,10 +173,6 @@ class Resolver(p.Stmt.Visitor[None], p.Expr.Visitor[None]):
self.resolve(expr.left)
self.resolve(expr.right)
def visit_set_expr(self, expr: p.SetExpr) -> None:
self.resolve(expr.value)
self.resolve(expr.object)
def visit_cast_expr(self, expr: p.CastExpr) -> None:
self.resolve(expr.expr)
-9
View File
@@ -20,7 +20,6 @@ from midas.ast.python import (
LogicalExpr,
MidasType,
ReturnStmt,
SetExpr,
Stmt,
TernaryExpr,
TypeAssign,
@@ -232,14 +231,6 @@ class PythonAstJsonSerializer(
"right": expr.right.accept(self),
}
def visit_set_expr(self, expr: SetExpr) -> dict:
return {
"_type": "SetExpr",
"object": expr.object.accept(self),
"name": expr.name,
"value": expr.value.accept(self),
}
def visit_cast_expr(self, expr: CastExpr) -> dict:
return {
"_type": "CastExpr",