feat(checker): implement lookup_member method

This commit is contained in:
2026-06-12 16:51:18 +02:00
parent 700284296c
commit 200709cca6
2 changed files with 66 additions and 68 deletions

View File

@@ -11,14 +11,11 @@ from midas.checker.registry import TypesRegistry
from midas.checker.reporter import FileReporter, Reporter 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,
ExtensionType,
Function, Function,
Operation, Operation,
Type, Type,
UnitType, UnitType,
UnknownType, UnknownType,
unfold_type,
) )
from midas.parser.python import PythonParser from midas.parser.python import PythonParser
@@ -250,8 +247,7 @@ class PythonTyper(
self._assign_var(location, target, value_type) self._assign_var(location, target, value_type)
case p.GetExpr(object=object, name=name): case p.GetExpr(object=object, name=name):
object_type: Type = self.type_of(object) self._assign_attr(location, object, name, value_type)
self._assign_attr(location, object_type, name, value_type)
case _: case _:
if not isinstance(target, p.VariableExpr): if not isinstance(target, p.VariableExpr):
@@ -277,42 +273,18 @@ class PythonTyper(
) )
def _assign_attr( def _assign_attr(
self, location: Location, object: Type, name: str, value_type: Type self, location: Location, object: p.Expr, name: str, value_type: Type
): ):
# TODO: improve recursion to have better error messages object_type: Type = self.type_of(object)
base_object: Type = unfold_type(object) member: Optional[Type] = self.types.lookup_member(object_type, name)
match base_object: if member is None:
case ComplexType(members=members): self.reporter.error(location, f"Unknown member '{name}' of {object_type}")
if name not in members:
self.reporter.error(location, f"Unknown member '{object}.{name}'")
return return
self.logger.debug(f"Member '{name}' of {object_type} has type {member}")
member_type: Type = members[name] if not self.is_subtype(value_type, member):
if not self.is_subtype(value_type, member_type):
self.reporter.error( self.reporter.error(
location, location,
f"Cannot assign {value_type} to member '{object}.{name}' of type {member_type}", f"Cannot assign {value_type} to member '{object_type}.{name}' of type {member}",
)
return
case ExtensionType(base=base, extension=ComplexType(members=members)):
if name in members:
member_type: Type = members[name]
if not self.is_subtype(value_type, member_type):
self.reporter.error(
location,
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():
pass
case _:
self.reporter.error(
location,
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:
@@ -433,39 +405,15 @@ 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) member: Optional[Type] = self.types.lookup_member(object, expr.name)
if member is None: if member is None:
self.reporter.error( self.reporter.error(
expr.location, f"Unknown property '{expr.name}' on {object}" expr.location, f"Unknown member '{expr.name}' of {object}"
) )
return UnknownType() return UnknownType()
self.logger.debug(f"Property '{expr.name}' on {object} has type {member}") self.logger.debug(f"Member '{expr.name}' of {object} has type {member}")
return member return member
def _get_member(self, object: Type, name: str) -> Optional[Type]:
base_object: Type = unfold_type(object)
match base_object:
case ComplexType(members=members):
if name in members:
return members[name]
self.logger.debug(f"No property '{name}' in {base_object}")
return None
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():
return UnknownType()
case _:
self.logger.debug(f"Can't get property on {base_object}")
return UnknownType()
def visit_literal_expr(self, expr: p.LiteralExpr) -> Type: def visit_literal_expr(self, expr: p.LiteralExpr) -> Type:
match expr.value: match expr.value:
case bool(): # Must be before int case bool(): # Must be before int

View File

@@ -7,11 +7,13 @@ from midas.checker.types import (
AppliedType, AppliedType,
BaseType, BaseType,
ComplexType, ComplexType,
ExtensionType,
Function, Function,
GenericType, GenericType,
Operation, Operation,
OverloadedFunction, OverloadedFunction,
Type, Type,
UnknownType,
substitute_typevars, substitute_typevars,
) )
@@ -337,3 +339,51 @@ class TypesRegistry:
reduced = True reduced = True
break break
return [types[i] for i in keep] return [types[i] for i in keep]
def lookup_member(self, type: Type, member_name: str) -> Optional[Type]:
match type:
case AliasType(name=name, type=base):
if name in self._members:
if member_name in self._members[name]:
return self._members[name][member_name]
return self.lookup_member(base, member_name)
case AppliedType(name=name, body=body, args=args):
generic: Type = self.get_type(name)
if not isinstance(generic, GenericType):
raise ValueError("AppliedType not derived from a GenericType")
substitutions = {
type_var.name: arg for arg, type_var in zip(args, generic.params)
}
if name in self._members:
if member_name in self._members[name]:
member_type: Type = self._members[name][member_name]
return substitute_typevars(member_type, substitutions)
member_type2: Optional[Type] = self.lookup_member(body, member_name)
if member_type2 is not None:
member_type2 = substitute_typevars(member_type2, substitutions)
return member_type2
case ComplexType(members=members):
if member_name in members:
return members[member_name]
self.logger.debug(f"No member '{member_name}' in {type}")
return None
case ExtensionType(base=base, extension=ComplexType(members=members)):
if member_name in members:
return members[member_name]
self.logger.debug(
f"No member '{member_name}' on {type}, looking up in base"
)
return self.lookup_member(base, member_name)
case UnknownType():
return UnknownType()
case _:
self.logger.debug(f"Can't get member on {type}")
return None