From 653612ee87c5679105fd41f425ed247d7f1451f2 Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Thu, 9 Jul 2026 00:02:25 +0200 Subject: [PATCH] feat(checker): add variance manager to improve recursive types --- midas/checker/midas.py | 8 ++-- midas/checker/variance.py | 79 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/midas/checker/midas.py b/midas/checker/midas.py index 02eb547..cfa881d 100644 --- a/midas/checker/midas.py +++ b/midas/checker/midas.py @@ -24,7 +24,7 @@ from midas.checker.types import ( TypeVar, UnknownType, ) -from midas.checker.variance import VarianceInferrer +from midas.checker.variance import VarianceManager from midas.lexer.midas import MidasLexer from midas.lexer.token import Token, TokenType 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: stmt.accept(self) - for name, type in self.types._types.items(): - if isinstance(type, GenericType): - inferrer = VarianceInferrer(self.types) - self.types._types[name] = inferrer.infer(type) + manager: VarianceManager = VarianceManager(self.types) + manager.infer_all() def assert_bool(self, expr: m.Expr): """Check that the given expression is a subtype of `bool` or report an error diff --git a/midas/checker/variance.py b/midas/checker/variance.py index bcbdc2b..5a7bfc6 100644 --- a/midas/checker/variance.py +++ b/midas/checker/variance.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Literal, Optional, cast from midas.checker.registry import Member, TypesRegistry @@ -73,10 +75,14 @@ class Tracker: class VarianceInferrer: """Helper class to compute type parameter variance""" - def __init__(self, types: TypesRegistry) -> None: - self.types: TypesRegistry = types + def __init__(self, manager: VarianceManager) -> None: + self.manager: VarianceManager = manager self.tracker: Tracker = Tracker([]) + @property + def types(self) -> TypesRegistry: + return self.manager.types + def infer(self, type: GenericType) -> GenericType: """Infer the variance of a generic type's parameters @@ -150,11 +156,13 @@ class VarianceInferrer: # Get inferred variance of parameters and multiply with current # polarity to recurse through arguments case AppliedType(name=name, args=args): - # TODO: handle mutually recursive types - if name == base_name: + if self.manager.is_in_queue(name): return + generic: Type = self.types.get_type(name) assert isinstance(generic, GenericType) + generic = self.manager.infer(name, generic) + params: list[TypeVar] = generic.params polarities: dict[Variance, Polarity] = { Variance.INVARIANT: 0, @@ -179,3 +187,66 @@ class VarianceInferrer: case TypeVar(): if type in self.tracker: 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