feat(checker): add variance manager to improve recursive types

This commit is contained in:
2026-07-09 00:02:25 +02:00
parent 2df6bca948
commit 653612ee87
2 changed files with 78 additions and 9 deletions

View File

@@ -24,7 +24,7 @@ from midas.checker.types import (
TypeVar, TypeVar,
UnknownType, UnknownType,
) )
from midas.checker.variance import VarianceInferrer from midas.checker.variance import VarianceManager
from midas.lexer.midas import MidasLexer from midas.lexer.midas import MidasLexer
from midas.lexer.token import Token, TokenType from midas.lexer.token import Token, TokenType
from midas.parser.midas import MidasParser from midas.parser.midas import MidasParser
@@ -147,10 +147,8 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
for stmt in stmts: for stmt in stmts:
stmt.accept(self) stmt.accept(self)
for name, type in self.types._types.items(): manager: VarianceManager = VarianceManager(self.types)
if isinstance(type, GenericType): manager.infer_all()
inferrer = VarianceInferrer(self.types)
self.types._types[name] = inferrer.infer(type)
def assert_bool(self, expr: m.Expr): def assert_bool(self, expr: m.Expr):
"""Check that the given expression is a subtype of `bool` or report an error """Check that the given expression is a subtype of `bool` or report an error

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from typing import Literal, Optional, cast from typing import Literal, Optional, cast
from midas.checker.registry import Member, TypesRegistry from midas.checker.registry import Member, TypesRegistry
@@ -73,10 +75,14 @@ class Tracker:
class VarianceInferrer: class VarianceInferrer:
"""Helper class to compute type parameter variance""" """Helper class to compute type parameter variance"""
def __init__(self, types: TypesRegistry) -> None: def __init__(self, manager: VarianceManager) -> None:
self.types: TypesRegistry = types self.manager: VarianceManager = manager
self.tracker: Tracker = Tracker([]) self.tracker: Tracker = Tracker([])
@property
def types(self) -> TypesRegistry:
return self.manager.types
def infer(self, type: GenericType) -> GenericType: def infer(self, type: GenericType) -> GenericType:
"""Infer the variance of a generic type's parameters """Infer the variance of a generic type's parameters
@@ -150,11 +156,13 @@ class VarianceInferrer:
# Get inferred variance of parameters and multiply with current # Get inferred variance of parameters and multiply with current
# polarity to recurse through arguments # polarity to recurse through arguments
case AppliedType(name=name, args=args): case AppliedType(name=name, args=args):
# TODO: handle mutually recursive types if self.manager.is_in_queue(name):
if name == base_name:
return return
generic: Type = self.types.get_type(name) generic: Type = self.types.get_type(name)
assert isinstance(generic, GenericType) assert isinstance(generic, GenericType)
generic = self.manager.infer(name, generic)
params: list[TypeVar] = generic.params params: list[TypeVar] = generic.params
polarities: dict[Variance, Polarity] = { polarities: dict[Variance, Polarity] = {
Variance.INVARIANT: 0, Variance.INVARIANT: 0,
@@ -179,3 +187,66 @@ class VarianceInferrer:
case TypeVar(): case TypeVar():
if type in self.tracker: if type in self.tracker:
self.tracker.record(type, polarity) self.tracker.record(type, polarity)
class VarianceManager:
"""Coordinator for VarianceInferrer to handle recursive types"""
def __init__(self, types: TypesRegistry) -> None:
self.types: TypesRegistry = types
self._queue: list[str] = []
self._inferred: set[str] = set()
def infer_all(self):
"""Infer variance on all generic types defined in the registry"""
for name, type in self.types._types.items():
if isinstance(type, GenericType):
self.infer(name, type)
def infer(self, name: str, type: GenericType) -> GenericType:
"""Infer variance of parameters of the given type
Args:
name (str): the type's name
type (GenericType): the type
Returns:
GenericType: a new generic type with its parameters updated with
their inferred variance
"""
if self.is_inferred(name):
return type
self._queue.append(name)
inferrer: VarianceInferrer = VarianceInferrer(self)
inferred: GenericType = inferrer.infer(type)
self.types._types[name] = inferred
self._queue.pop()
self._inferred.add(name)
return inferred
def is_in_queue(self, name: str) -> bool:
"""Whether the given type's variance is currently being inferred
Args:
name (str): the type's name
Returns:
bool: whether the type is in the queue
"""
return name in self._queue
def is_inferred(self, name: str) -> bool:
"""Whether the given type's variance has already been inferred
Args:
name (str): the type's name
Returns:
bool: whether the type has been processed
"""
return name in self._inferred