feat(checker): add variance manager to improve recursive types
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user