3 Commits
Author SHA1 Message Date
HEL b8bb8190c4 fix(resolver): define variable on assignment
if a variable is not already defined when an assignment is visited, it is then defined in the current scope
2026-06-09 08:06:46 +02:00
HEL a4f5db7ece fix(checker): use reduce_types to infer return type 2026-06-09 08:05:31 +02:00
HEL fc67f01f34 refactor(checker): extract reduce_types function 2026-06-09 08:04:45 +02:00
3 changed files with 50 additions and 27 deletions
+8 -25
View File
@@ -204,13 +204,13 @@ class PythonTyper(
inferred_return: Type = UnknownType()
if not returned:
env.return_types.append(UnitType())
return_types: set[Type] = set(env.return_types)
return_types: list[Type] = self.types.reduce_types(env.return_types)
if len(return_types) == 1:
inferred_return = list(return_types)[0]
inferred_return = return_types[0]
elif len(return_types) > 1:
self.reporter.error(
stmt.location,
f"Mixed return types: {env.return_types}",
f"Mixed return types: {return_types}",
)
returns: Type = UnknownType()
@@ -502,34 +502,17 @@ class PythonTyper(
def visit_list_expr(self, expr: p.ListExpr) -> Type:
list_type: Type = self.types.get_type("list")
item_types: list[Type] = [self.type_of(item) for item in expr.items]
item_types = self.types.reduce_types(item_types)
# Try to reduce types with subsumption
reduced: bool = True
keep: list[int] = list(range(len(item_types)))
while reduced:
reduced = False
for i, i1 in enumerate(keep):
type1: Type = item_types[i1]
for i2 in keep[i + 1 :]:
type2 = item_types[i2]
if self.types.is_subtype(type1, type2):
keep.remove(i1)
elif self.types.is_subtype(type2, type1):
keep.remove(i2)
else:
continue
reduced = True
break
if len(keep) == 0:
if len(item_types) == 0:
return list_type
if len(keep) == 1:
item_type: Type = item_types[keep[0]]
if len(item_types) == 1:
item_type: Type = item_types[0]
return self.types.apply_generic(list_type, [item_type])
self.reporter.error(
expr.location,
f"Heterogeneous list items: {[item_types[i] for i in keep]}",
f"Heterogeneous list items: {item_types}",
)
return self.types.apply_generic(list_type, [UnknownType()])
+28
View File
@@ -283,3 +283,31 @@ class TypesRegistry:
case _:
raise ValueError(f"{type} is not a generic type")
def reduce_types(self, types: list[Type]) -> list[Type]:
"""Reduce a list of types to remove subtypes and only keep the highest types
Args:
types (list[Type]): the types to reduce
Returns:
list[Type]: the reduced list of types
"""
reduced: bool = True
keep: list[int] = list(range(len(types)))
while reduced:
reduced = False
for i, i1 in enumerate(keep):
type1: Type = types[i1]
for i2 in keep[i + 1 :]:
type2 = types[i2]
if self.is_subtype(type1, type2):
keep.remove(i1)
elif self.is_subtype(type2, type1):
keep.remove(i2)
else:
continue
reduced = True
break
return [types[i] for i in keep]
+14 -2
View File
@@ -13,7 +13,7 @@ class Resolver(p.Stmt.Visitor[None], p.Expr.Visitor[None]):
def __init__(self):
self.locals: dict[p.Expr, int] = {}
self.scopes: list[dict[str, bool]] = []
self.scopes: list[dict[str, bool]] = [{}]
def resolve(self, *objects: p.Stmt | p.Expr) -> None:
"""Resolve the given statements or expressions"""
@@ -77,6 +77,12 @@ class Resolver(p.Stmt.Visitor[None], p.Expr.Visitor[None]):
self.locals[expr] = i
return
def is_defined(self, name: str) -> bool:
for scope in self.scopes:
if name in scope:
return True
return False
def resolve_function(self, function: p.Function) -> None:
"""Resolve a function definition
@@ -111,7 +117,13 @@ class Resolver(p.Stmt.Visitor[None], p.Expr.Visitor[None]):
self.resolve(stmt.value)
for target in stmt.targets:
match target:
case p.VariableExpr() | p.GetExpr():
case p.VariableExpr(name=name):
if not self.is_defined(name):
self.declare(name)
self.define(name)
target.accept(self)
case p.GetExpr():
target.accept(self)
case _:
raise Exception(f"Unsupported assignment to {target}")