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