feat(checker): add formula to compute aggregation type
This commit is contained in:
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import ast
|
import ast
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, Callable, Optional, Union
|
||||||
|
|
||||||
import midas.ast.python as p
|
import midas.ast.python as p
|
||||||
from midas.ast.location import Location
|
from midas.ast.location import Location
|
||||||
@@ -24,6 +24,9 @@ from midas.checker.types import (
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from midas.checker.python import TypedExpr
|
from midas.checker.python import TypedExpr
|
||||||
|
|
||||||
|
FormulaOperand = Union["Formula", str, Type]
|
||||||
|
Formula = Union[Type, tuple[FormulaOperand, str, FormulaOperand]]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class Call:
|
class Call:
|
||||||
@@ -44,6 +47,29 @@ class Call:
|
|||||||
class ColumnMethodRegistry(MethodRegistry[Call]):
|
class ColumnMethodRegistry(MethodRegistry[Call]):
|
||||||
"""The method registry for column types"""
|
"""The method registry for column types"""
|
||||||
|
|
||||||
|
def _resolve_formula_operand(self, call: Call, operand: FormulaOperand) -> Type:
|
||||||
|
match operand:
|
||||||
|
case str():
|
||||||
|
return self.types.get_type(operand)
|
||||||
|
case (_, _, _):
|
||||||
|
return self._resolve_formula_type(call, operand)
|
||||||
|
case _:
|
||||||
|
return operand
|
||||||
|
|
||||||
|
def _resolve_formula_type(self, call: Call, formula: Formula) -> Type:
|
||||||
|
if not isinstance(formula, tuple):
|
||||||
|
return formula
|
||||||
|
op1, operator, op2 = formula
|
||||||
|
op1_type: Type = self._resolve_formula_operand(call, op1)
|
||||||
|
op2_type: Type = self._resolve_formula_operand(call, op2)
|
||||||
|
return self.typer.result_of_binary_op(
|
||||||
|
location=call.location,
|
||||||
|
expr=call.call_expr,
|
||||||
|
left=(call.column_expr, op1_type),
|
||||||
|
right=(call.column_expr, op2_type),
|
||||||
|
method=operator,
|
||||||
|
)
|
||||||
|
|
||||||
def _simple_call(self, call: Call, function: Type) -> Type:
|
def _simple_call(self, call: Call, function: Type) -> Type:
|
||||||
"""Get the result of calling a simple method
|
"""Get the result of calling a simple method
|
||||||
|
|
||||||
@@ -274,7 +300,7 @@ class ColumnMethodRegistry(MethodRegistry[Call]):
|
|||||||
call: Call,
|
call: Call,
|
||||||
kwargs: list[Function.Parameter] = [],
|
kwargs: list[Function.Parameter] = [],
|
||||||
*,
|
*,
|
||||||
preserve_inner_type: bool = False,
|
formula: Optional[Callable[[Type], Formula]] = None,
|
||||||
) -> Type:
|
) -> Type:
|
||||||
"""Compute the result type of an aggregate method call
|
"""Compute the result type of an aggregate method call
|
||||||
|
|
||||||
@@ -289,6 +315,10 @@ class ColumnMethodRegistry(MethodRegistry[Call]):
|
|||||||
Returns:
|
Returns:
|
||||||
Type: the result type
|
Type: the result type
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
returns: Type = ColumnType(type=TopType())
|
||||||
|
if formula:
|
||||||
|
returns = self._resolve_formula_type(call, formula(call.column.type))
|
||||||
signature = Function(
|
signature = Function(
|
||||||
params=ParamSpec(
|
params=ParamSpec(
|
||||||
kw=[
|
kw=[
|
||||||
@@ -301,7 +331,7 @@ class ColumnMethodRegistry(MethodRegistry[Call]):
|
|||||||
*kwargs,
|
*kwargs,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returns=call.column if preserve_inner_type else ColumnType(type=TopType()),
|
returns=returns,
|
||||||
)
|
)
|
||||||
|
|
||||||
result: CallResult = self.dispatcher.get_result(
|
result: CallResult = self.dispatcher.get_result(
|
||||||
@@ -318,27 +348,29 @@ class ColumnMethodRegistry(MethodRegistry[Call]):
|
|||||||
|
|
||||||
@method()
|
@method()
|
||||||
def max(self, call: Call) -> Type:
|
def max(self, call: Call) -> Type:
|
||||||
return self._aggregate(call, preserve_inner_type=True)
|
return self._aggregate(call, formula=lambda t: t)
|
||||||
|
|
||||||
@method()
|
@method()
|
||||||
def mean(self, call: Call) -> Type:
|
def mean(self, call: Call) -> Type:
|
||||||
return self._aggregate(call)
|
return self._aggregate(
|
||||||
|
call, formula=lambda t: ((t, "__add__", t), "__truediv__", "int")
|
||||||
|
)
|
||||||
|
|
||||||
@method()
|
@method()
|
||||||
def median(self, call: Call) -> Type:
|
def median(self, call: Call) -> Type:
|
||||||
return self._aggregate(call, preserve_inner_type=True)
|
return self._aggregate(call, formula=lambda t: t)
|
||||||
|
|
||||||
@method()
|
@method()
|
||||||
def min(self, call: Call) -> Type:
|
def min(self, call: Call) -> Type:
|
||||||
return self._aggregate(call, preserve_inner_type=True)
|
return self._aggregate(call, formula=lambda t: t)
|
||||||
|
|
||||||
@method()
|
@method()
|
||||||
def mode(self, call: Call) -> Type:
|
def mode(self, call: Call) -> Type:
|
||||||
return self._aggregate(call, preserve_inner_type=True)
|
return self._aggregate(call, formula=lambda t: t)
|
||||||
|
|
||||||
@method("product", "prod")
|
@method("product", "prod")
|
||||||
def product(self, call: Call) -> Type:
|
def product(self, call: Call) -> Type:
|
||||||
return self._aggregate(call)
|
return self._aggregate(call, formula=lambda t: (t, "__mul__", t))
|
||||||
|
|
||||||
@method()
|
@method()
|
||||||
def std(self, call: Call) -> Type:
|
def std(self, call: Call) -> Type:
|
||||||
@@ -356,7 +388,7 @@ class ColumnMethodRegistry(MethodRegistry[Call]):
|
|||||||
|
|
||||||
@method()
|
@method()
|
||||||
def sum(self, call: Call) -> Type:
|
def sum(self, call: Call) -> Type:
|
||||||
return self._aggregate(call)
|
return self._aggregate(call, formula=lambda t: (t, "__add__", t))
|
||||||
|
|
||||||
@method()
|
@method()
|
||||||
def var(self, call: Call) -> Type:
|
def var(self, call: Call) -> Type:
|
||||||
|
|||||||
Reference in New Issue
Block a user