5 Commits
Author SHA1 Message Date
HEL 2886ffe00b feat(checker): add slice overloads on lists 2026-06-14 17:04:29 +02:00
HEL def38e720b fix(checker): handle generic overloads 2026-06-14 17:04:10 +02:00
HEL 49274be2f4 feat(checker): type check slice expressions 2026-06-14 16:58:20 +02:00
HEL aec6b7aa7b feat(parser): add slice expression 2026-06-14 16:53:38 +02:00
HEL 530d93723e tests: update with new subscript and call checks
invalid function calls now return UnknownType even if the function has a return type
2026-06-14 16:45:53 +02:00
15 changed files with 236 additions and 32 deletions
@@ -33,3 +33,5 @@ a = foo[0]
b = bar[0][1]
c = bar[0][1][2] # invalid, not method __getitem__ on Meter
c = bar[""] # invalid, wrong index type
d = foo[1:2]
+6
View File
@@ -148,4 +148,10 @@ class SubscriptExpr:
index: Expr
class SliceExpr:
lower: Optional[Expr]
upper: Optional[Expr]
step: Optional[Expr]
###<
+7
View File
@@ -729,3 +729,10 @@ class PythonAstPrinter(
self._write_line("index", last=True)
with self._child_level(single=True):
expr.index.accept(self)
def visit_slice_expr(self, expr: p.SliceExpr) -> None:
self._write_line("SliceExpr")
with self._child_level():
self._write_optional_child("lower", expr.lower)
self._write_optional_child("upper", expr.upper)
self._write_optional_child("step", expr.step, last=True)
+13
View File
@@ -227,6 +227,9 @@ class Expr(ABC):
@abstractmethod
def visit_subscript_expr(self, expr: SubscriptExpr) -> T: ...
@abstractmethod
def visit_slice_expr(self, expr: SliceExpr) -> T: ...
@dataclass(frozen=True)
class BinaryExpr(Expr):
@@ -336,3 +339,13 @@ class SubscriptExpr(Expr):
def accept(self, visitor: Expr.Visitor[T]) -> T:
return visitor.visit_subscript_expr(self)
@dataclass(frozen=True)
class SliceExpr(Expr):
lower: Optional[Expr]
upper: Optional[Expr]
step: Optional[Expr]
def accept(self, visitor: Expr.Visitor[T]) -> T:
return visitor.visit_slice_expr(self)
+3 -3
View File
@@ -129,11 +129,11 @@ extend list[T] {
def __len__: fn () -> int
// def __iter__: fn () -> Iterator[T]
def __getitem__: fn (i: int, /) -> T
//__getitem__: fn (s: slice, /) -> list[T]
def __getitem__: fn (s: slice, /) -> list[T]
def __setitem__: fn (key: int, value: T, /) -> None
//__setitem__: fn (key: slice, value: list[T], /) -> None
def __setitem__: fn (key: slice, value: list[T], /) -> None
def __delitem__: fn (key: int, /) -> None
// def __delitem__: fn (key: slice, /) -> None
def __delitem__: fn (key: slice, /) -> None
// def __add__: fn[S <: T] (value: list[S], /) -> list[T]
def __add__: fn (value: list[T], /) -> list[T]
def __iadd__: fn (value: list[T], /) -> list[T]
+1
View File
@@ -29,6 +29,7 @@ def define_builtins(reg: TypesRegistry):
int = reg.define_type("int", BaseType(name="int"))
float = reg.define_type("float", BaseType(name="float"))
str = reg.define_type("str", BaseType(name="str"))
slice = reg.define_type("slice", BaseType(name="slice"))
list = reg.define_type(
"list",
+5 -1
View File
@@ -507,6 +507,9 @@ class PythonTyper(
expr.location, operation, [(expr.index, index)], {}
)
def visit_slice_expr(self, expr: p.SliceExpr) -> Type:
return self.types.get_type("slice")
def visit_base_type(self, node: p.BaseType) -> Type:
base: Type
try:
@@ -661,9 +664,10 @@ class PythonTyper(
# No match -> invalid call
if n_candidates == 0:
overloads_str: str = ", ".join(map(str, overloads))
self.reporter.error(
location,
f"No matching overload in {overloads} {for_args}",
f"No matching overload in [{overloads_str}] {for_args}",
)
return None
+8
View File
@@ -200,3 +200,11 @@ class Resolver(p.Stmt.Visitor[None], p.Expr.Visitor[None]):
def visit_subscript_expr(self, expr: p.SubscriptExpr) -> None:
self.resolve(expr.object)
self.resolve(expr.index)
def visit_slice_expr(self, expr: p.SliceExpr) -> None:
if expr.lower is not None:
self.resolve(expr.lower)
if expr.upper is not None:
self.resolve(expr.upper)
if expr.step is not None:
self.resolve(expr.step)
+8
View File
@@ -164,6 +164,14 @@ def substitute_typevars(type: Type, substitutions: dict[str, Type]) -> Type:
returns=substitute_typevars(returns, substitutions),
)
case OverloadedFunction(overloads=overloads):
return OverloadedFunction(
overloads=[
substitute_typevars(overload, substitutions)
for overload in overloads
]
)
case ComplexType(members=members):
members2: dict[str, Type] = {
name: substitute_typevars(prop, substitutions)
+8
View File
@@ -222,6 +222,14 @@ class PythonHighlighter(
expr.object.accept(self)
expr.index.accept(self)
def visit_slice_expr(self, expr: p.SliceExpr) -> None:
if expr.lower is not None:
expr.lower.accept(self)
if expr.upper is not None:
expr.upper.accept(self)
if expr.step is not None:
expr.step.accept(self)
class MidasHighlighter(
Highlighter, m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[None]
+9
View File
@@ -22,6 +22,7 @@ from midas.ast.python import (
LogicalExpr,
MidasType,
ReturnStmt,
SliceExpr,
Stmt,
SubscriptExpr,
TernaryExpr,
@@ -431,6 +432,14 @@ class PythonParser:
index=self.parse_expr(index),
)
case ast.Slice(lower=lower, upper=upper, step=step):
return SliceExpr(
location=location,
lower=self.parse_expr(lower) if lower is not None else None,
upper=self.parse_expr(upper) if upper is not None else None,
step=self.parse_expr(step) if step is not None else None,
)
case _:
raise UnsupportedSyntaxError(node)
@@ -1,4 +1,19 @@
{
"diagnostics": [],
"diagnostics": [
{
"type": "Warning",
"location": {
"start": [
6,
4
],
"end": [
13,
5
]
},
"message": "FrameType not yet supported"
}
],
"judgments": []
}
+9 -27
View File
@@ -326,9 +326,7 @@
"arguments": [],
"keywords": {}
},
"type": {
"name": "bool"
}
"type": {}
},
{
"location": {
@@ -407,9 +405,7 @@
],
"keywords": {}
},
"type": {
"name": "bool"
}
"type": {}
},
{
"location": {
@@ -505,9 +501,7 @@
],
"keywords": {}
},
"type": {
"name": "bool"
}
"type": {}
},
{
"location": {
@@ -604,9 +598,7 @@
}
}
},
"type": {
"name": "bool"
}
"type": {}
},
{
"location": {
@@ -719,9 +711,7 @@
],
"keywords": {}
},
"type": {
"name": "bool"
}
"type": {}
},
{
"location": {
@@ -835,9 +825,7 @@
}
}
},
"type": {
"name": "bool"
}
"type": {}
},
{
"location": {
@@ -916,9 +904,7 @@
}
}
},
"type": {
"name": "bool"
}
"type": {}
},
{
"location": {
@@ -997,9 +983,7 @@
}
}
},
"type": {
"name": "bool"
}
"type": {}
},
{
"location": {
@@ -1461,9 +1445,7 @@
}
}
},
"type": {
"name": "bool"
}
"type": {}
}
]
}
@@ -18,6 +18,80 @@
]
}
},
{
"_type": "TypeAssign",
"name": "lat",
"type": {
"_type": "BaseType",
"base": "Column",
"param": {
"_type": "BaseType",
"base": "GeoLocation",
"param": null
}
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "lat"
}
],
"value": {
"_type": "GetExpr",
"object": {
"_type": "SubscriptExpr",
"object": {
"_type": "VariableExpr",
"name": "df"
},
"index": {
"_type": "LiteralExpr",
"value": "location"
}
},
"name": "lat"
}
},
{
"_type": "TypeAssign",
"name": "lon",
"type": {
"_type": "BaseType",
"base": "Column",
"param": {
"_type": "BaseType",
"base": "GeoLocation",
"param": null
}
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "lon"
}
],
"value": {
"_type": "GetExpr",
"object": {
"_type": "SubscriptExpr",
"object": {
"_type": "VariableExpr",
"name": "df"
},
"index": {
"_type": "LiteralExpr",
"value": "location"
}
},
"name": "lon"
}
},
{
"_type": "ExpressionStmt",
"expr": {
@@ -33,6 +107,64 @@
}
}
},
{
"_type": "TypeAssign",
"name": "lat1",
"type": {
"_type": "BaseType",
"base": "Latitude",
"param": null
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "lat1"
}
],
"value": {
"_type": "SubscriptExpr",
"object": {
"_type": "VariableExpr",
"name": "lat"
},
"index": {
"_type": "LiteralExpr",
"value": 0
}
}
},
{
"_type": "TypeAssign",
"name": "lat2",
"type": {
"_type": "BaseType",
"base": "Latitude",
"param": null
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "lat2"
}
],
"value": {
"_type": "SubscriptExpr",
"object": {
"_type": "VariableExpr",
"name": "lat"
},
"index": {
"_type": "LiteralExpr",
"value": 1
}
}
},
{
"_type": "TypeAssign",
"name": "lat_diff",
+9
View File
@@ -21,6 +21,7 @@ from midas.ast.python import (
LogicalExpr,
MidasType,
ReturnStmt,
SliceExpr,
Stmt,
SubscriptExpr,
TernaryExpr,
@@ -260,3 +261,11 @@ class PythonAstJsonSerializer(
"object": expr.object.accept(self),
"index": expr.index.accept(self),
}
def visit_slice_expr(self, expr: SliceExpr) -> dict:
return {
"_type": "SliceExpr",
"lower": self._serialize_optional(expr.lower),
"upper": self._serialize_optional(expr.upper),
"step": self._serialize_optional(expr.step),
}