Compare commits
36 Commits
cccf2f8f9f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f7c43837b5 | |||
|
32ed62a6f1
|
|||
|
66f39acec0
|
|||
|
6c04e2fee4
|
|||
| 2bb2e0a684 | |||
|
5630320d21
|
|||
|
9f05ba3224
|
|||
|
5fbe965919
|
|||
| 252a5abdfd | |||
|
55fba6a088
|
|||
|
70ce263ea2
|
|||
|
e1d5eac8b8
|
|||
|
82666a4918
|
|||
|
45f84a2f23
|
|||
|
dedfcb4dbb
|
|||
|
d9ea6365ea
|
|||
| 9c7a93412c | |||
|
d6b8fbfb60
|
|||
|
b290c59ac4
|
|||
|
093f2bc477
|
|||
|
7c771c4070
|
|||
|
a50a207385
|
|||
|
7e5ea5e414
|
|||
|
0ba0266bae
|
|||
|
216c80f08c
|
|||
|
f75d7722a1
|
|||
|
2f29c47274
|
|||
|
80af2b9048
|
|||
|
577454ee7e
|
|||
|
878693383e
|
|||
|
0b91de75a8
|
|||
| 739871c101 | |||
|
4395e9339b
|
|||
|
29e601128d
|
|||
|
b591f5508f
|
|||
|
41d0c84bbe
|
15
examples/02_demonstration/demo.midas
Normal file
15
examples/02_demonstration/demo.midas
Normal file
@@ -0,0 +1,15 @@
|
||||
predicate in_range(min: float, max: float)(v: float) = min <= v & v <= max
|
||||
predicate is_ratio = in_range(0, 1)
|
||||
|
||||
type Currency = float
|
||||
type Price[T <: Currency] = T where _ >= 0
|
||||
|
||||
extend Price[T <: Currency] {
|
||||
def __add__: fn(Price[T], /) -> Price[T]
|
||||
}
|
||||
|
||||
type EUR = Currency
|
||||
type USD = Currency
|
||||
type CHF = Currency
|
||||
|
||||
type Discount = float where is_ratio(_)
|
||||
35
examples/02_demonstration/demo.py
Normal file
35
examples/02_demonstration/demo.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from typing import TypeVar
|
||||
|
||||
from demo_stubs import CHF, EUR, USD, Currency, Discount, Price
|
||||
|
||||
from midas.typing import cast, unsafe_cast
|
||||
|
||||
T = TypeVar("T", bound=Currency)
|
||||
|
||||
|
||||
def apply_discount(amount: Price[T], discount: Discount) -> Price[T]:
|
||||
return cast(Price[T], (1.0 - discount) * amount)
|
||||
|
||||
|
||||
a1 = cast(Price[EUR], 3.2)
|
||||
a2 = cast(Price[USD], 10.4)
|
||||
r1 = cast(Discount, 0.2)
|
||||
|
||||
print(apply_discount(a1, r1))
|
||||
print(apply_discount(a2, r1))
|
||||
|
||||
a3 = a1 + a1
|
||||
a4 = a1 + a2 # cannot add euros and dollars
|
||||
a3 = a2 # cannot change variable type
|
||||
|
||||
dyn_price = float(input("Price (CHF): "))
|
||||
dyn_discount = float(input("Discount (0.0-1.0): "))
|
||||
discounted = apply_discount(
|
||||
cast(Price[CHF], dyn_price),
|
||||
cast(Discount, dyn_discount),
|
||||
)
|
||||
|
||||
print(f"Discounted: CHF {discounted}")
|
||||
|
||||
large_data = [i * 10 for i in range(100)]
|
||||
prices = unsafe_cast(list[Price[EUR]], large_data)
|
||||
14
examples/02_demonstration/demo_stubs.pyi
Normal file
14
examples/02_demonstration/demo_stubs.pyi
Normal file
@@ -0,0 +1,14 @@
|
||||
from __future__ import annotations
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
class Currency(float): ...
|
||||
|
||||
_T0 = TypeVar("_T0", bound=Currency, covariant=True)
|
||||
|
||||
class Price(Currency, Generic[_T0]):
|
||||
def __add__(self, _0: Price[_T0], /) -> Price[_T0]: ...
|
||||
|
||||
class EUR(Currency): ...
|
||||
class USD(Currency): ...
|
||||
class CHF(Currency): ...
|
||||
class Discount(float): ...
|
||||
@@ -145,6 +145,7 @@ class LogicalExpr:
|
||||
class CastExpr:
|
||||
type: MidasType
|
||||
expr: Expr
|
||||
unsafe: bool
|
||||
|
||||
|
||||
class TernaryExpr:
|
||||
|
||||
@@ -757,9 +757,10 @@ class PythonAstPrinter(
|
||||
self._write_line("type")
|
||||
with self._child_level(single=True):
|
||||
expr.type.accept(self)
|
||||
self._write_line("expr", last=True)
|
||||
self._write_line("expr")
|
||||
with self._child_level(single=True):
|
||||
expr.expr.accept(self)
|
||||
self._write_line(f"unsafe: {expr.unsafe}", last=True)
|
||||
|
||||
def visit_ternary_expr(self, expr: p.TernaryExpr) -> None:
|
||||
self._write_line("TernaryExpr")
|
||||
|
||||
@@ -350,6 +350,7 @@ class LogicalExpr(Expr):
|
||||
class CastExpr(Expr):
|
||||
type: MidasType
|
||||
expr: Expr
|
||||
unsafe: bool
|
||||
|
||||
def accept(self, visitor: Expr.Visitor[T]) -> T:
|
||||
return visitor.visit_cast_expr(self)
|
||||
|
||||
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
BUILTIN_SUBTYPES: dict[str, set[str]] = {
|
||||
"object": {"float", "list", "dict"},
|
||||
"object": {"float", "list", "dict", "str"},
|
||||
"float": {"int"},
|
||||
"int": {"bool"},
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ class DiagnosticType(StrEnum):
|
||||
ERROR = "Error"
|
||||
WARNING = "Warning"
|
||||
INFO = "Info"
|
||||
DEBUG = "Debug"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
172
midas/checker/evaluator.py
Normal file
172
midas/checker/evaluator.py
Normal file
@@ -0,0 +1,172 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
import midas.ast.midas as m
|
||||
from midas.checker.preamble import Preamble
|
||||
from midas.checker.registry import TypesRegistry
|
||||
from midas.checker.reporter import FileReporter
|
||||
from midas.checker.types import Function, Predicate
|
||||
from midas.lexer.token import TokenType
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class PartialPredicate(Predicate):
|
||||
scope: dict[str, Any]
|
||||
|
||||
|
||||
class Evaluator(m.Expr.Visitor[Any]):
|
||||
def __init__(self, types: TypesRegistry, reporter: Optional[FileReporter] = None):
|
||||
self.types: TypesRegistry = types
|
||||
self.reporter: Optional[FileReporter] = reporter
|
||||
self.preamble: Preamble = Preamble(self.types)
|
||||
self.scopes: list[dict[str, Any]] = [{}]
|
||||
|
||||
def evaluate(self, expr: m.Expr) -> Any:
|
||||
value: Any = expr.accept(self)
|
||||
if self.reporter is not None:
|
||||
self.reporter.debug(expr.location, f"Value: {value}")
|
||||
return value
|
||||
|
||||
def get_value(self, name: str) -> Any:
|
||||
scope: dict[str, Any] = self.scopes[-1]
|
||||
return scope[name]
|
||||
|
||||
def set_value(self, name: str, value: Any, force_declare: bool = False):
|
||||
if not force_declare:
|
||||
for scope in reversed(self.scopes):
|
||||
if name in scope:
|
||||
scope[name] = value
|
||||
return
|
||||
self.scopes[-1][name] = value
|
||||
|
||||
def visit_logical_expr(self, expr: m.LogicalExpr) -> Any:
|
||||
def left():
|
||||
return self.evaluate(expr.left)
|
||||
|
||||
def right():
|
||||
return self.evaluate(expr.right)
|
||||
|
||||
match expr.operator.type:
|
||||
case TokenType.AND:
|
||||
return left() and right()
|
||||
case _:
|
||||
raise NotImplementedError
|
||||
|
||||
def visit_binary_expr(self, expr: m.BinaryExpr) -> Any:
|
||||
left: Any = self.evaluate(expr.left)
|
||||
right: Any = self.evaluate(expr.right)
|
||||
match expr.operator.type:
|
||||
case TokenType.MINUS:
|
||||
return left - right
|
||||
case TokenType.STAR:
|
||||
return left * right
|
||||
case TokenType.SLASH:
|
||||
return left / right
|
||||
case TokenType.GREATER:
|
||||
return left > right
|
||||
case TokenType.GREATER_EQUAL:
|
||||
return left >= right
|
||||
case TokenType.LESS:
|
||||
return left < right
|
||||
case TokenType.LESS_EQUAL:
|
||||
return left <= right
|
||||
case TokenType.EQUAL_EQUAL:
|
||||
return left == right
|
||||
case TokenType.BANG_EQUAL:
|
||||
return left != right
|
||||
case _:
|
||||
raise NotImplementedError
|
||||
|
||||
def visit_unary_expr(self, expr: m.UnaryExpr) -> Any:
|
||||
right: Any = self.evaluate(expr.right)
|
||||
match expr.operator.type:
|
||||
case TokenType.MINUS:
|
||||
return -right
|
||||
case _:
|
||||
raise NotImplementedError
|
||||
|
||||
def visit_call_expr(self, expr: m.CallExpr) -> Any:
|
||||
callee: Any = self.evaluate(expr.callee)
|
||||
args: list[Any] = [self.evaluate(arg) for arg in expr.arguments]
|
||||
kwargs: dict[str, Any] = {
|
||||
name: self.evaluate(arg) for name, arg in expr.keywords.items()
|
||||
}
|
||||
|
||||
match callee:
|
||||
case Predicate():
|
||||
return self._evaluate_predicate(callee, args, kwargs)
|
||||
case _ if callable(callee):
|
||||
return callee(*args, **kwargs)
|
||||
case _:
|
||||
return NotImplementedError
|
||||
|
||||
def visit_get_expr(self, expr: m.GetExpr) -> Any:
|
||||
obj: Any = self.evaluate(expr.expr)
|
||||
return getattr(obj, expr.name.lexeme)
|
||||
|
||||
def visit_variable_expr(self, expr: m.VariableExpr) -> Any:
|
||||
name: str = expr.name.lexeme
|
||||
for scope in reversed(self.scopes):
|
||||
if name in scope:
|
||||
return scope[name]
|
||||
|
||||
predicate: Optional[Predicate] = self.types.lookup_predicate(name)
|
||||
if predicate is not None:
|
||||
if predicate.alias:
|
||||
return self.evaluate(predicate.body)
|
||||
return predicate
|
||||
|
||||
glob: Optional[Callable] = self.preamble.get_py_func(name)
|
||||
if glob is not None:
|
||||
return glob
|
||||
raise NameError(f"Unknown variable '{name}'")
|
||||
|
||||
def visit_grouping_expr(self, expr: m.GroupingExpr) -> Any:
|
||||
return self.evaluate(expr.expr)
|
||||
|
||||
def visit_literal_expr(self, expr: m.LiteralExpr) -> Any:
|
||||
return expr.value
|
||||
|
||||
def visit_wildcard_expr(self, expr: m.WildcardExpr) -> Any:
|
||||
return self.get_value("_")
|
||||
|
||||
def _evaluate_predicate(
|
||||
self, predicate: Predicate, args: list[Any], kwargs: dict[str, Any]
|
||||
) -> Any:
|
||||
res: Any = None
|
||||
if isinstance(predicate, PartialPredicate):
|
||||
self.scopes.append(predicate.scope)
|
||||
else:
|
||||
self.scopes.append({})
|
||||
match predicate.type:
|
||||
case Function(returns=Function() as inner):
|
||||
self._map_args(predicate.type, args, kwargs)
|
||||
res = PartialPredicate(
|
||||
type=inner,
|
||||
body=predicate.body,
|
||||
alias=False,
|
||||
scope=self.scopes[-1],
|
||||
)
|
||||
|
||||
case Function():
|
||||
self._map_args(predicate.type, args, kwargs)
|
||||
res = self.evaluate(predicate.body)
|
||||
|
||||
case _:
|
||||
raise NotImplementedError
|
||||
self.scopes.pop()
|
||||
return res
|
||||
|
||||
def _map_args(self, function: Function, args: list[Any], kwargs: dict[str, Any]):
|
||||
positional: list[Function.Argument] = function.pos_args + function.args
|
||||
keywords: dict[str, Function.Argument] = {
|
||||
arg.name: arg for arg in function.args + function.kw_args
|
||||
}
|
||||
|
||||
for i, arg in enumerate(args):
|
||||
param: Function.Argument = positional[i]
|
||||
self.set_value(param.name, arg)
|
||||
|
||||
for name, arg in kwargs.items():
|
||||
param: Function.Argument = keywords[name]
|
||||
self.set_value(param.name, arg)
|
||||
@@ -1,4 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Optional
|
||||
|
||||
from midas.checker.environment import Environment
|
||||
from midas.checker.registry import TypesRegistry
|
||||
@@ -16,16 +17,18 @@ class Preamble(Environment):
|
||||
def __init__(self, types: TypesRegistry) -> None:
|
||||
super().__init__()
|
||||
self._types: TypesRegistry = types
|
||||
self._python_funcs: dict[str, Callable] = {}
|
||||
|
||||
self._def_type_constructor("object")
|
||||
self._def_type_constructor("float")
|
||||
self._def_type_constructor("int")
|
||||
self._def_type_constructor("bool")
|
||||
self._def_type_constructor("str")
|
||||
self._def_type_constructor("object", object)
|
||||
self._def_type_constructor("float", float)
|
||||
self._def_type_constructor("int", int)
|
||||
self._def_type_constructor("bool", bool)
|
||||
self._def_type_constructor("str", str)
|
||||
self._def_function(
|
||||
name="list",
|
||||
pos=[Param("object", TopType())],
|
||||
returns=self._list_of(TopType()),
|
||||
py_function=list,
|
||||
)
|
||||
|
||||
# TODO: use sink
|
||||
@@ -33,6 +36,7 @@ class Preamble(Environment):
|
||||
name="print",
|
||||
pos=[Param("object", TopType())],
|
||||
returns=UnitType(),
|
||||
py_function=print,
|
||||
)
|
||||
|
||||
map_in = TypeVar(name="T", bound=None)
|
||||
@@ -52,17 +56,25 @@ class Preamble(Environment):
|
||||
),
|
||||
],
|
||||
returns=self._list_of(map_out), # TODO: replace with Iterable[U]
|
||||
type_vars=[map_in, map_out],
|
||||
py_function=map,
|
||||
)
|
||||
self._def_function(
|
||||
name="input",
|
||||
pos=[Param("prompt", TopType(), required=False)],
|
||||
returns=self._types.get_type("str"),
|
||||
)
|
||||
|
||||
def _list_of(self, item_type: Type) -> Type:
|
||||
return self._types.apply_generic(self._types.get_type("list"), [item_type])
|
||||
|
||||
def _def_type_constructor(self, name: str):
|
||||
def _def_type_constructor(self, name: str, py_function: Optional[Callable] = None):
|
||||
# TODO: more specific arg types
|
||||
self._def_function(
|
||||
name=name,
|
||||
pos=[Param("object", TopType(), required=False)],
|
||||
returns=self._types.get_type(name),
|
||||
py_function=py_function,
|
||||
)
|
||||
|
||||
def _make_function(
|
||||
@@ -109,6 +121,7 @@ class Preamble(Environment):
|
||||
kw: list[Param] = [],
|
||||
returns: Type = UnitType(),
|
||||
type_vars: list[TypeVar] = [],
|
||||
py_function: Optional[Callable] = None,
|
||||
):
|
||||
function: Type = self._make_function(
|
||||
name=name,
|
||||
@@ -119,3 +132,8 @@ class Preamble(Environment):
|
||||
type_vars=type_vars,
|
||||
)
|
||||
self.define(name, function)
|
||||
if py_function is not None:
|
||||
self._python_funcs[name] = py_function
|
||||
|
||||
def get_py_func(self, name: str) -> Optional[Callable]:
|
||||
return self._python_funcs.get(name)
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import ast
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
import midas.ast.python as p
|
||||
from midas.ast.location import Location
|
||||
from midas.ast.printer import MidasPrinter
|
||||
from midas.checker.environment import Environment
|
||||
from midas.checker.evaluator import Evaluator
|
||||
from midas.checker.operators import (
|
||||
PY_COMPARATOR_METHODS,
|
||||
PY_OPERATOR_METHODS,
|
||||
@@ -18,13 +20,19 @@ from midas.checker.resolver import Resolver
|
||||
from midas.checker.types import (
|
||||
AliasType,
|
||||
AppliedType,
|
||||
BaseType,
|
||||
ConstraintType,
|
||||
Function,
|
||||
GenericType,
|
||||
OverloadedFunction,
|
||||
Type,
|
||||
TypeVar,
|
||||
UnitType,
|
||||
UnknownType,
|
||||
Variance,
|
||||
unfold_type,
|
||||
)
|
||||
from midas.checker.unifier import Unifier
|
||||
from midas.parser.python import PythonParser
|
||||
from midas.utils import TypedAST
|
||||
|
||||
@@ -67,6 +75,7 @@ class PythonTyper(
|
||||
self.env: Environment = self.global_env
|
||||
self.locals: dict[p.Expr, int] = {}
|
||||
self.judgements: list[tuple[p.Expr, Type]] = []
|
||||
self.evaluated_casts: list[p.CastExpr] = []
|
||||
|
||||
def process(self, source: str, path: Optional[str]) -> TypedAST:
|
||||
self.reporter = self.reporter.for_file(path)
|
||||
@@ -80,10 +89,15 @@ class PythonTyper(
|
||||
self.env = self.global_env
|
||||
self.locals = resolver.locals
|
||||
self.judgements = []
|
||||
self.evaluated_casts = []
|
||||
|
||||
self.check(stmts)
|
||||
|
||||
return TypedAST(stmts=stmts, judgements=self.judgements)
|
||||
return TypedAST(
|
||||
stmts=stmts,
|
||||
judgements=self.judgements,
|
||||
evaluated_casts=self.evaluated_casts,
|
||||
)
|
||||
|
||||
def judge(self, expr: p.Expr, type: Type):
|
||||
"""Record a typing judgement
|
||||
@@ -227,7 +241,8 @@ class PythonTyper(
|
||||
)
|
||||
pos += 1
|
||||
|
||||
for arg in pos_args + args + kw_args:
|
||||
all_args: list[Function.Argument] = pos_args + args + kw_args
|
||||
for arg in all_args:
|
||||
env.define(arg.name, arg.type)
|
||||
|
||||
returns_hint: Optional[Type] = None
|
||||
@@ -268,12 +283,25 @@ class PythonTyper(
|
||||
returns = inferred_return
|
||||
|
||||
# TODO: handle *args and **kwargs sinks
|
||||
function: Function = Function(
|
||||
function: Type = Function(
|
||||
pos_args=pos_args,
|
||||
args=args,
|
||||
kw_args=kw_args,
|
||||
returns=returns,
|
||||
)
|
||||
generic_params: list[TypeVar] = []
|
||||
all_types: list[Type] = [arg.type for arg in all_args] + [returns]
|
||||
for type in all_types:
|
||||
if isinstance(type, TypeVar):
|
||||
if type not in generic_params:
|
||||
generic_params.append(type)
|
||||
|
||||
if len(generic_params) != 0:
|
||||
function = GenericType(
|
||||
name=stmt.name,
|
||||
params=generic_params,
|
||||
body=function,
|
||||
)
|
||||
self.env.define(stmt.name, function)
|
||||
|
||||
def visit_type_assign(self, stmt: p.TypeAssign) -> None:
|
||||
@@ -451,6 +479,10 @@ class PythonTyper(
|
||||
return result or UnknownType()
|
||||
|
||||
def visit_call_expr(self, expr: p.CallExpr) -> Type:
|
||||
match expr.callee:
|
||||
case p.VariableExpr(name="TypeVar"):
|
||||
return self.define_typevar(expr) or UnknownType()
|
||||
|
||||
callee: Type = self.type_of(expr.callee)
|
||||
positional: list[TypedExpr] = [
|
||||
(arg, self.type_of(arg)) for arg in expr.arguments
|
||||
@@ -516,7 +548,16 @@ class PythonTyper(
|
||||
return UnknownType()
|
||||
|
||||
def visit_cast_expr(self, expr: p.CastExpr) -> Type:
|
||||
return self.resolve_type_expr(expr.type)
|
||||
subject_type: Type = self.type_of(expr.expr)
|
||||
target_type: Type = self.resolve_type_expr(expr.type)
|
||||
is_lit, lit_value = self._get_literal(expr.expr)
|
||||
if is_lit:
|
||||
evaluated: bool = self._evaluate_cast_statically(
|
||||
expr, subject_type, target_type, lit_value
|
||||
)
|
||||
if evaluated:
|
||||
self.evaluated_casts.append(expr)
|
||||
return target_type
|
||||
|
||||
def visit_ternary_expr(self, expr: p.TernaryExpr) -> Type:
|
||||
test_type: Type = self.type_of(expr.test)
|
||||
@@ -704,6 +745,28 @@ class PythonTyper(
|
||||
location, base, positional, keywords, report_errors
|
||||
)
|
||||
|
||||
case GenericType():
|
||||
unifier: Unifier = Unifier(self.types)
|
||||
pos: list[Type] = [a[1] for a in positional]
|
||||
kw: dict[str, Type] = {k: v[1] for k, v in keywords.items()}
|
||||
unified: Optional[Type] = unifier.unify_call(callee, pos, kw)
|
||||
if unified is None:
|
||||
if report_errors:
|
||||
pos_str: str = ", ".join(str(t) for t in pos)
|
||||
kw_str: str = ", ".join(f"{k}: {v}" for k, v in kw.items())
|
||||
self.reporter.error(
|
||||
location,
|
||||
f"Could not unify {callee}={callee.body} with pos=[{pos_str}] and kw={{{kw_str}}}",
|
||||
)
|
||||
return None
|
||||
return self._get_call_result(
|
||||
location,
|
||||
unified,
|
||||
positional,
|
||||
keywords,
|
||||
report_errors,
|
||||
)
|
||||
|
||||
case _:
|
||||
if report_errors:
|
||||
self.reporter.error(
|
||||
@@ -1009,3 +1072,147 @@ class PythonTyper(
|
||||
report_errors=False,
|
||||
)
|
||||
return result
|
||||
|
||||
def define_typevar(self, call: p.CallExpr) -> Optional[TypeVar]:
|
||||
def is_kw_true(name: str) -> bool:
|
||||
match call.keywords.get(name):
|
||||
case p.LiteralExpr(value=True):
|
||||
return True
|
||||
case _:
|
||||
return False
|
||||
|
||||
match call:
|
||||
case p.CallExpr(
|
||||
arguments=[p.LiteralExpr(value=str() as name)],
|
||||
):
|
||||
bound: Optional[Type] = None
|
||||
variance: Variance = Variance.INVARIANT
|
||||
if "bound" in call.keywords:
|
||||
bound_type: p.MidasType = self._parse_type_from_expr(
|
||||
call.keywords["bound"]
|
||||
)
|
||||
bound = self.resolve_type_expr(bound_type)
|
||||
|
||||
if is_kw_true("covariant"):
|
||||
variance = Variance.COVARIANT
|
||||
|
||||
if is_kw_true("contravariant"):
|
||||
if variance == Variance.COVARIANT:
|
||||
self.reporter.warning(
|
||||
call.keywords["contravariant"].location,
|
||||
"TypeVar cannot be covariant and contravariant at the same time. Marked as invariant",
|
||||
)
|
||||
variance = Variance.INVARIANT
|
||||
else:
|
||||
variance = Variance.CONTRAVARIANT
|
||||
var: TypeVar = TypeVar(name=name, bound=bound, variance=variance)
|
||||
self.types.define_type(name, var)
|
||||
return var
|
||||
|
||||
case _:
|
||||
self.reporter.warning(
|
||||
call.location, "Invalid usage of 'TypeVar', skipping"
|
||||
)
|
||||
return None
|
||||
|
||||
def _parse_type_from_expr(self, expr: p.Expr) -> p.MidasType:
|
||||
location: Location = expr.location
|
||||
parser = PythonParser()
|
||||
match expr:
|
||||
case p.LiteralExpr(value=str() as value):
|
||||
node: ast.Expression = ast.parse(value, mode="eval")
|
||||
return parser._parse_type(node.body)
|
||||
case p.VariableExpr(name=name):
|
||||
return p.BaseType(location=location, base=name, param=None)
|
||||
case _:
|
||||
raise NotImplementedError
|
||||
|
||||
def _get_literal(self, expr: p.Expr) -> tuple[bool, Any]:
|
||||
match expr:
|
||||
case p.LiteralExpr(value=value):
|
||||
return True, value
|
||||
|
||||
case p.ListExpr(items=items):
|
||||
values: list[Any] = []
|
||||
for item in items:
|
||||
is_lit, value = self._get_literal(item)
|
||||
if not is_lit:
|
||||
return False, None
|
||||
values.append(value)
|
||||
return True, values
|
||||
|
||||
case p.DictExpr(keys=keys, values=values):
|
||||
pairs: list[tuple[Any, Any]] = []
|
||||
for key, value in zip(keys, values):
|
||||
key_val = None
|
||||
if key is not None:
|
||||
is_lit, key_val = self._get_literal(key)
|
||||
if not is_lit:
|
||||
return False, None
|
||||
|
||||
is_lit, value_val = self._get_literal(value)
|
||||
if not is_lit:
|
||||
return False, None
|
||||
|
||||
if key is None:
|
||||
# TODO: check that value is always a dict
|
||||
assert isinstance(value_val, dict)
|
||||
pairs.extend(value_val.items())
|
||||
else:
|
||||
pairs.append((key_val, value_val))
|
||||
return True, dict(pairs)
|
||||
|
||||
case _:
|
||||
return False, None
|
||||
|
||||
def _evaluate_cast_statically(
|
||||
self, expr: p.CastExpr, subject_type: Type, target_type: Type, lit_value: Any
|
||||
) -> bool:
|
||||
match target_type:
|
||||
case AliasType(type=base):
|
||||
return self._evaluate_cast_statically(
|
||||
expr, subject_type, base, lit_value
|
||||
)
|
||||
|
||||
case AppliedType(body=body):
|
||||
return self._evaluate_cast_statically(
|
||||
expr, subject_type, body, lit_value
|
||||
)
|
||||
|
||||
case ConstraintType(type=base, constraint=constraint):
|
||||
evaluated: bool = True
|
||||
if not self._evaluate_cast_statically(
|
||||
expr, subject_type, base, lit_value
|
||||
):
|
||||
evaluated = False
|
||||
|
||||
evaluator = Evaluator(self.types)
|
||||
evaluator.set_value("_", lit_value)
|
||||
res = evaluator.evaluate(constraint)
|
||||
if not res:
|
||||
printer = MidasPrinter()
|
||||
constraint_str: str = printer.print(constraint)
|
||||
self.reporter.error(
|
||||
expr.location,
|
||||
f"Value {lit_value!r} does not fit constraint '{constraint_str}'",
|
||||
)
|
||||
evaluated = False
|
||||
return evaluated
|
||||
|
||||
case BaseType():
|
||||
# TODO: do we want to allow cast(float, int)? would require runtime conversion
|
||||
if not self.types.is_subtype(
|
||||
subject_type, target_type
|
||||
) or not self.types.is_subtype(target_type, subject_type):
|
||||
self.reporter.error(
|
||||
expr.location,
|
||||
f"Value {lit_value!r} of type {subject_type} cannot be cast as {target_type}",
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
case _:
|
||||
self.reporter.info(
|
||||
expr.location, f"Cannot evaluate cast to {target_type} statically"
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -130,6 +130,19 @@ class TypesRegistry:
|
||||
case (_, TopType()):
|
||||
return True
|
||||
|
||||
case (_, UnknownType()):
|
||||
return True
|
||||
|
||||
case (TypeVar(bound=bound), _):
|
||||
if bound is None:
|
||||
return False
|
||||
return self.is_subtype(bound, type2)
|
||||
|
||||
case (_, TypeVar(bound=bound)):
|
||||
if bound is None:
|
||||
return True
|
||||
return self.is_subtype(type1, bound)
|
||||
|
||||
case (AliasType(type=base1), _):
|
||||
return self.is_subtype(base1, type2)
|
||||
|
||||
@@ -147,11 +160,6 @@ class TypesRegistry:
|
||||
case (Function(), Function()):
|
||||
return self.is_func_subtype(type1, type2)
|
||||
|
||||
case (TypeVar(bound=bound), _):
|
||||
if bound is None:
|
||||
return False
|
||||
return self.is_subtype(bound, type2)
|
||||
|
||||
case (ConstraintType(type=base1), _):
|
||||
return self.is_subtype(base1, type2)
|
||||
|
||||
@@ -173,6 +181,10 @@ class TypesRegistry:
|
||||
return False
|
||||
return True
|
||||
|
||||
# TODO: verify legitimacy
|
||||
case (AppliedType(body=body), _):
|
||||
return self.is_subtype(body, type2)
|
||||
|
||||
return False
|
||||
|
||||
# TODO: verify the logic in here
|
||||
@@ -389,6 +401,12 @@ class TypesRegistry:
|
||||
)
|
||||
return self.lookup_member(base, member_name)
|
||||
|
||||
case ConstraintType(type=base):
|
||||
return self.lookup_member(base, member_name)
|
||||
|
||||
case TypeVar(bound=bound) if bound is not None:
|
||||
return self.lookup_member(bound, member_name)
|
||||
|
||||
case UnknownType():
|
||||
return UnknownType()
|
||||
|
||||
|
||||
@@ -61,3 +61,10 @@ class FileReporter:
|
||||
location=location,
|
||||
message=message,
|
||||
)
|
||||
|
||||
def debug(self, location: Location, message: str):
|
||||
self.report(
|
||||
type=DiagnosticType.DEBUG,
|
||||
location=location,
|
||||
message=message,
|
||||
)
|
||||
|
||||
169
midas/checker/unifier.py
Normal file
169
midas/checker/unifier.py
Normal file
@@ -0,0 +1,169 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from midas.checker.registry import TypesRegistry
|
||||
from midas.checker.types import (
|
||||
AppliedType,
|
||||
Function,
|
||||
GenericType,
|
||||
TopType,
|
||||
Type,
|
||||
TypeVar,
|
||||
)
|
||||
|
||||
|
||||
class UnificationError(Exception): ...
|
||||
|
||||
|
||||
class Unifier:
|
||||
def __init__(self, types: TypesRegistry) -> None:
|
||||
self.types: TypesRegistry = types
|
||||
self.logger: logging.Logger = logging.getLogger("Unifier")
|
||||
|
||||
def unify_call(
|
||||
self,
|
||||
type: GenericType,
|
||||
positional: list[Type],
|
||||
keywords: dict[str, Type],
|
||||
) -> Optional[Type]:
|
||||
concrete_func: Function = Function(
|
||||
pos_args=[
|
||||
Function.Argument(
|
||||
pos=i,
|
||||
name=str(i),
|
||||
type=arg,
|
||||
required=True,
|
||||
)
|
||||
for i, arg in enumerate(positional)
|
||||
],
|
||||
args=[],
|
||||
kw_args=[
|
||||
Function.Argument(
|
||||
pos=len(positional) + i,
|
||||
name=name,
|
||||
type=arg,
|
||||
required=True,
|
||||
)
|
||||
for i, (name, arg) in enumerate(keywords.items())
|
||||
],
|
||||
returns=TopType(), # TODO: use expected type
|
||||
)
|
||||
return self.unify_generic(type, concrete_func, match_return=False)
|
||||
|
||||
def unify_generic(
|
||||
self,
|
||||
template: GenericType,
|
||||
concrete: Type,
|
||||
match_return: bool = True,
|
||||
) -> Optional[Type]:
|
||||
substitutions: dict[str, Type]
|
||||
try:
|
||||
substitutions = self.match(template.body, concrete, match_return)
|
||||
except UnificationError:
|
||||
return None
|
||||
|
||||
args: list[Type] = []
|
||||
for param in template.params:
|
||||
if param.name not in substitutions:
|
||||
return None
|
||||
args.append(substitutions[param.name])
|
||||
|
||||
applied: Type = self.types.apply_generic(template, args)
|
||||
return applied
|
||||
|
||||
def match(
|
||||
self,
|
||||
template: Type,
|
||||
concrete: Type,
|
||||
match_return: bool = True,
|
||||
) -> dict[str, Type]:
|
||||
# TODO: if concrete is Generic, record bound TypeVar. Then when merging
|
||||
# substitutions, check that the constraint is respected
|
||||
match (template, concrete):
|
||||
case (TypeVar(name=name), _):
|
||||
return {name: concrete}
|
||||
|
||||
case (
|
||||
AppliedType(name=template_name, args=template_args),
|
||||
AppliedType(name=concrete_name, args=concrete_args),
|
||||
) if template_name == concrete_name and len(template_args) == len(
|
||||
concrete_args
|
||||
):
|
||||
substitutions: dict[str, Type] = {}
|
||||
for template_arg, concrete_arg in zip(template_args, concrete_args):
|
||||
new_substistutions: dict[str, Type] = self.match(
|
||||
template_arg, concrete_arg
|
||||
)
|
||||
substitutions = self.merge(substitutions, new_substistutions)
|
||||
|
||||
return substitutions
|
||||
|
||||
case (Function(), Function()):
|
||||
mapped: list[tuple[Function.Argument, Function.Argument]] = (
|
||||
self.map_params(template, concrete)
|
||||
)
|
||||
substitutions: dict[str, Type] = {}
|
||||
for template_arg, concrete_arg in mapped:
|
||||
arg_subs: dict[str, Type] = self.match(
|
||||
template_arg.type, concrete_arg.type
|
||||
)
|
||||
substitutions = self.merge(substitutions, arg_subs)
|
||||
|
||||
if match_return:
|
||||
return_subs: dict[str, Type] = self.match(
|
||||
template.returns, concrete.returns
|
||||
)
|
||||
substitutions = self.merge(substitutions, return_subs)
|
||||
|
||||
return substitutions
|
||||
|
||||
case _:
|
||||
self.logger.debug(f"Can't match {concrete!r} with {template!r}")
|
||||
return {}
|
||||
|
||||
def merge(self, subs1: dict[str, Type], subs2: dict[str, Type]) -> dict[str, Type]:
|
||||
merged: dict[str, Type] = subs1.copy()
|
||||
|
||||
for k, v in subs2.items():
|
||||
if k in merged and merged[k] != v:
|
||||
self.logger.debug(
|
||||
f"Substitution already defined for {k} with type {merged[k]}, got {v}"
|
||||
)
|
||||
raise UnificationError
|
||||
merged[k] = v
|
||||
return merged
|
||||
|
||||
def map_params(
|
||||
self, func1: Function, func2: Function
|
||||
) -> list[tuple[Function.Argument, Function.Argument]]:
|
||||
pos1: list[Function.Argument] = func1.pos_args
|
||||
mixed1: list[Function.Argument] = func1.args
|
||||
kw1: list[Function.Argument] = func1.kw_args
|
||||
|
||||
pos2: list[Function.Argument] = func2.pos_args
|
||||
mixed2: list[Function.Argument] = func2.args
|
||||
kw2: list[Function.Argument] = func2.kw_args
|
||||
|
||||
mapped: list[tuple[Function.Argument, Function.Argument]] = []
|
||||
|
||||
by_pos2: dict[int, Function.Argument] = {arg.pos: arg for arg in pos2 + mixed2}
|
||||
by_name2: dict[str, Function.Argument] = {arg.name: arg for arg in mixed2 + kw2}
|
||||
|
||||
for arg1 in pos1:
|
||||
if (arg2 := by_pos2.get(arg1.pos)) is not None:
|
||||
mapped.append((arg1, arg2))
|
||||
|
||||
for arg1 in mixed1:
|
||||
# Match both positionally and by name, conflicts are caught
|
||||
# when merging substitutions
|
||||
if (arg2 := by_pos2.get(arg1.pos)) is not None:
|
||||
mapped.append((arg1, arg2))
|
||||
|
||||
if (arg2 := by_name2.get(arg1.name)) is not None:
|
||||
mapped.append((arg1, arg2))
|
||||
|
||||
for arg1 in kw1:
|
||||
if (arg2 := by_name2.get(arg1.name)) is not None:
|
||||
mapped.append((arg1, arg2))
|
||||
|
||||
return mapped
|
||||
@@ -19,9 +19,11 @@ from midas.utils import TypedAST
|
||||
@click.command(help="Compile source")
|
||||
@click.argument("file", type=click.File("r"))
|
||||
@click.option("-t", "--types", type=click.File("r"), multiple=True)
|
||||
@click.option("--ignore-errors", is_flag=True)
|
||||
def compile(
|
||||
file: TextIO,
|
||||
types: tuple[TextIO],
|
||||
ignore_errors: bool,
|
||||
):
|
||||
source: str = file.read()
|
||||
source_path: Path = Path(file.name).resolve()
|
||||
@@ -35,7 +37,9 @@ def compile(
|
||||
printer = DiagnosticPrinter()
|
||||
printer.print_all(diagnostics)
|
||||
|
||||
if any(map(lambda d: d.type == DiagnosticType.ERROR, diagnostics)):
|
||||
if not ignore_errors and any(
|
||||
map(lambda d: d.type == DiagnosticType.ERROR, diagnostics)
|
||||
):
|
||||
sys.exit(1)
|
||||
|
||||
generator = Generator(workdir=source_path.parent, types=checker.types)
|
||||
|
||||
@@ -1,27 +1,64 @@
|
||||
import ast
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import TextIO
|
||||
|
||||
import black
|
||||
import click
|
||||
from watchdog.events import DirModifiedEvent, FileModifiedEvent, FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from midas.checker.checker import TypeChecker
|
||||
from midas.generator.stubs import StubsGenerator
|
||||
|
||||
|
||||
@click.command(help="Generate stubs from Midas definitions")
|
||||
@click.argument("file", type=click.File("r"))
|
||||
@click.option("-o", "--output", type=click.File("w"), default="-")
|
||||
def stubs(
|
||||
file: TextIO,
|
||||
output: TextIO,
|
||||
):
|
||||
source_path: Path = Path(file.name).resolve()
|
||||
|
||||
def generate_stubs(in_path: Path, out_path: Path):
|
||||
checker = TypeChecker()
|
||||
checker.import_midas(source_path)
|
||||
checker.import_midas(in_path)
|
||||
|
||||
generator = StubsGenerator(checker.types)
|
||||
module: ast.Module = generator.generate_stubs()
|
||||
module = ast.fix_missing_locations(module)
|
||||
|
||||
output.write(ast.unparse(module))
|
||||
output: str = ast.unparse(module)
|
||||
output = black.format_str(output, mode=black.Mode(is_pyi=True))
|
||||
|
||||
out_path.write_text(output)
|
||||
|
||||
|
||||
class Handler(FileSystemEventHandler):
|
||||
def __init__(self, in_path: Path, out_path: Path) -> None:
|
||||
super().__init__()
|
||||
self.in_path: Path = in_path
|
||||
self.out_path: Path = out_path
|
||||
|
||||
def on_modified(self, event: DirModifiedEvent | FileModifiedEvent) -> None:
|
||||
generate_stubs(self.in_path, self.out_path)
|
||||
|
||||
|
||||
@click.command(help="Generate stubs from Midas definitions")
|
||||
@click.argument("file", type=click.File("r"))
|
||||
@click.option("-o", "--output", type=click.File("w"), default="-")
|
||||
@click.option("-w", "--watch", is_flag=True)
|
||||
def stubs(
|
||||
file: TextIO,
|
||||
output: TextIO,
|
||||
watch: bool,
|
||||
):
|
||||
source_path: Path = Path(file.name).resolve()
|
||||
out_path: Path = Path(output.name).resolve()
|
||||
generate_stubs(source_path, out_path)
|
||||
|
||||
if watch:
|
||||
print(f"Watching {source_path}...")
|
||||
print("Press CTRL+C to stop")
|
||||
handler = Handler(source_path, out_path)
|
||||
observer = Observer()
|
||||
observer.schedule(handler, str(source_path))
|
||||
observer.start()
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
observer.stop()
|
||||
observer.join()
|
||||
|
||||
@@ -41,6 +41,7 @@ def types(
|
||||
message=f"Type: {type}",
|
||||
)
|
||||
)
|
||||
diagnostics.extend(checker.diagnostics)
|
||||
printer = DiagnosticPrinter()
|
||||
printer.print_all(diagnostics)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
@@ -7,6 +8,13 @@ from midas.cli.ansi import Ansi
|
||||
|
||||
|
||||
class DiagnosticPrinter:
|
||||
COLORS: dict[DiagnosticType, int] = {
|
||||
DiagnosticType.ERROR: Ansi.RED,
|
||||
DiagnosticType.WARNING: Ansi.YELLOW,
|
||||
DiagnosticType.INFO: Ansi.CYAN,
|
||||
DiagnosticType.DEBUG: Ansi.MAGENTA,
|
||||
}
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.files: dict[Optional[str], list[str]] = {}
|
||||
|
||||
@@ -22,10 +30,25 @@ class DiagnosticPrinter:
|
||||
return self.files[filename]
|
||||
|
||||
def print_all(self, diagnostics: list[Diagnostic], indent: int = 4):
|
||||
by_type: dict[DiagnosticType, int] = defaultdict(int)
|
||||
for diagnostic in diagnostics:
|
||||
filename: Optional[str] = diagnostic.file_path
|
||||
lines = self.get_lines(filename)
|
||||
self.print(lines, diagnostic, indent=indent)
|
||||
by_type[diagnostic.type] += 1
|
||||
|
||||
if len(diagnostics) == 0:
|
||||
return
|
||||
|
||||
counts: list[str] = []
|
||||
for type in DiagnosticType:
|
||||
if type not in by_type:
|
||||
continue
|
||||
count: int = by_type[type]
|
||||
color: int = self.COLORS.get(type, Ansi.WHITE)
|
||||
counts.append(f"{Ansi.FG(color)}{type.value}s{Ansi.RESET}: {count}")
|
||||
|
||||
print(" ".join(counts))
|
||||
|
||||
def print(self, lines: list[str], diagnostic: Diagnostic, indent: int = 4):
|
||||
"""Pretty-print a diagnostic, showing some context if possible
|
||||
@@ -55,11 +78,7 @@ class DiagnosticPrinter:
|
||||
before: str = line[:start_offset]
|
||||
after: str = line[end_offset:]
|
||||
|
||||
color: int = {
|
||||
DiagnosticType.ERROR: Ansi.RED,
|
||||
DiagnosticType.WARNING: Ansi.YELLOW,
|
||||
DiagnosticType.INFO: Ansi.CYAN,
|
||||
}.get(diagnostic.type, Ansi.WHITE)
|
||||
color: int = self.COLORS.get(diagnostic.type, Ansi.WHITE)
|
||||
|
||||
subject: str = Ansi.FG(color) + line[start_offset:end_offset] + Ansi.RESET
|
||||
cursor: str = (
|
||||
|
||||
@@ -44,6 +44,7 @@ class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
|
||||
self._typed_ast: TypedAST = TypedAST(
|
||||
stmts=[],
|
||||
judgements=[],
|
||||
evaluated_casts=[],
|
||||
)
|
||||
self._alias_count: int = 0
|
||||
self._predicate_count: int = 0
|
||||
@@ -131,6 +132,10 @@ class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
|
||||
|
||||
def visit_cast_expr(self, expr: p.CastExpr) -> ast.expr:
|
||||
expr2: ast.expr = expr.expr.accept(self)
|
||||
|
||||
if expr in self._typed_ast.evaluated_casts or expr.unsafe:
|
||||
return expr2
|
||||
|
||||
alias: ast.expr = self._make_alias(expr2)
|
||||
|
||||
type: Type = self._get_expr_type(expr)
|
||||
@@ -322,8 +327,10 @@ class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
|
||||
self._make_cast_asserts(src_location, expr, base)
|
||||
self._make_constraint_assert(src_location, expr, constraint)
|
||||
|
||||
case TypeVar():
|
||||
raise RuntimeError("Unexpected TypeVar")
|
||||
case TypeVar(bound=bound):
|
||||
# TODO: check with type from arguments / use call-site context
|
||||
if bound is not None:
|
||||
self._make_cast_asserts(src_location, expr, bound)
|
||||
|
||||
case (
|
||||
TopType()
|
||||
|
||||
@@ -39,6 +39,18 @@ class StubsGenerator:
|
||||
self.stubs = []
|
||||
self.typing_imports = set()
|
||||
for name, type in self.types._types.items():
|
||||
# Skip builtin types, not just based on name so the user can override
|
||||
# TODO: check if added members on builtin type
|
||||
match type:
|
||||
case BaseType(name=name_) if name == name_:
|
||||
continue
|
||||
case GenericType(
|
||||
name=name1,
|
||||
body=BaseType(name=name2),
|
||||
) if (
|
||||
name == name1 == name2
|
||||
):
|
||||
continue
|
||||
self.generate_stub(name, type)
|
||||
|
||||
imports = [
|
||||
@@ -115,6 +127,12 @@ class StubsGenerator:
|
||||
body_subsitutions | substitutions,
|
||||
)
|
||||
|
||||
case ConstraintType(type=base):
|
||||
return self.get_bases(base)
|
||||
|
||||
case TypeVar(bound=bound) if bound is not None:
|
||||
return [self.dump_type(bound)], {}
|
||||
|
||||
case _:
|
||||
return [], {}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ class UnsupportedSyntaxError(Exception):
|
||||
|
||||
class PythonParser:
|
||||
CAST_FUNCTION = "cast"
|
||||
UNSAFE_CAST_FUNCTION = "unsafe_cast"
|
||||
|
||||
def parse_module(self, node: ast.Module) -> list[Stmt]:
|
||||
statements: list[Stmt] = []
|
||||
@@ -423,6 +424,9 @@ class PythonParser:
|
||||
case ast.Call(func=ast.Name(id=self.CAST_FUNCTION)):
|
||||
return self.parse_cast(node)
|
||||
|
||||
case ast.Call(func=ast.Name(id=self.UNSAFE_CAST_FUNCTION)):
|
||||
return self.parse_cast(node)
|
||||
|
||||
case ast.Call():
|
||||
return self.parse_call(node)
|
||||
|
||||
@@ -527,16 +531,19 @@ class PythonParser:
|
||||
return expr
|
||||
|
||||
def parse_cast(self, node: ast.Call) -> CastExpr:
|
||||
assert isinstance(node.func, ast.Name)
|
||||
func: str = node.func.id
|
||||
match node:
|
||||
case ast.Call(args=[type, expr], keywords=[]):
|
||||
return CastExpr(
|
||||
location=Location.from_ast(node),
|
||||
type=self._parse_type(type),
|
||||
expr=self.parse_expr(expr),
|
||||
unsafe=func == self.UNSAFE_CAST_FUNCTION,
|
||||
)
|
||||
case _:
|
||||
raise InvalidSyntaxError(
|
||||
f"Invalid call to {self.CAST_FUNCTION}, expected type and expression"
|
||||
f"Invalid call to {func}, expected type and expression"
|
||||
)
|
||||
|
||||
def parse_call(self, node: ast.Call) -> CallExpr:
|
||||
|
||||
34
midas/typing.py
Normal file
34
midas/typing.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from typing import cast as typing_cast
|
||||
|
||||
cast = typing_cast
|
||||
"""### Midas documentation
|
||||
Cast a value to a type.
|
||||
|
||||
- **Compile-time**: tells the type checker that the return value has the designated type.
|
||||
- **Run-time**: generates assertions to ensure the value can be interpreted as the given type.
|
||||
|
||||
---
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
_**Internal Python documentation**_
|
||||
"""
|
||||
|
||||
|
||||
unsafe_cast = typing_cast
|
||||
"""### Midas documentation
|
||||
Cast a value to a type.
|
||||
|
||||
- **Compile-time**: tells the type checker that the return value has the designated type.
|
||||
- **Run-time**: -
|
||||
|
||||
This operation is unsound, use at your own risk!
|
||||
|
||||
---
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
_**Internal Python documentation**_
|
||||
"""
|
||||
@@ -62,3 +62,4 @@ class UniversalJSONDumper:
|
||||
class TypedAST:
|
||||
stmts: list[p.Stmt]
|
||||
judgements: list[tuple[p.Expr, Type]]
|
||||
evaluated_casts: list[p.CastExpr]
|
||||
|
||||
@@ -8,7 +8,11 @@ authors = [
|
||||
{ name = "Louis Heredero", email = "louis.heredero@students.hevs.ch" },
|
||||
]
|
||||
classifiers = ["Programming Language :: Python :: 3"]
|
||||
dependencies = ["click>=8.4.1"]
|
||||
dependencies = [
|
||||
"black>=26.5.1",
|
||||
"click>=8.4.1",
|
||||
"watchdog>=6.0.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://git.kbk28.ch/HEL/midas"
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
{
|
||||
"diagnostics": [],
|
||||
"judgments": [
|
||||
{
|
||||
"location": {
|
||||
"from": "L4:30",
|
||||
"to": "L4:36"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 123.45
|
||||
},
|
||||
"type": {
|
||||
"name": "float"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L4:18",
|
||||
@@ -16,7 +29,8 @@
|
||||
"expr": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 123.45
|
||||
}
|
||||
},
|
||||
"unsafe": false
|
||||
},
|
||||
"type": {
|
||||
"name": "Meter",
|
||||
@@ -25,6 +39,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L5:28",
|
||||
"to": "L5:31"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 6.7
|
||||
},
|
||||
"type": {
|
||||
"name": "float"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L5:15",
|
||||
@@ -40,7 +67,8 @@
|
||||
"expr": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 6.7
|
||||
}
|
||||
},
|
||||
"unsafe": false
|
||||
},
|
||||
"type": {
|
||||
"name": "Second",
|
||||
|
||||
14
tests/cases/checker/08_unification.py
Normal file
14
tests/cases/checker/08_unification.py
Normal file
@@ -0,0 +1,14 @@
|
||||
def double(value: float) -> float:
|
||||
return value * 2
|
||||
|
||||
|
||||
def is_odd(value: int) -> bool:
|
||||
return bool(value % 2)
|
||||
|
||||
|
||||
floats: list[float] = [0.2, 0.5, 0.1, 1.2]
|
||||
ints: list[int] = [1, 2, 6, -3]
|
||||
|
||||
doubled_floats = map(double, floats)
|
||||
doubled_ints = map(double, ints)
|
||||
odd_ints = map(is_odd, ints)
|
||||
874
tests/cases/checker/08_unification.py.ref.json
Normal file
874
tests/cases/checker/08_unification.py.ref.json
Normal file
@@ -0,0 +1,874 @@
|
||||
{
|
||||
"diagnostics": [
|
||||
{
|
||||
"type": "Error",
|
||||
"location": {
|
||||
"start": [
|
||||
13,
|
||||
15
|
||||
],
|
||||
"end": [
|
||||
13,
|
||||
32
|
||||
]
|
||||
},
|
||||
"message": "Could not unify map[T, U]=(transform: (v: T, /) -> U, iterable: list[T], /) -> list[U] with pos=[(value: float) -> float, list[int]] and kw={}"
|
||||
}
|
||||
],
|
||||
"judgments": [
|
||||
{
|
||||
"location": {
|
||||
"from": "L2:11",
|
||||
"to": "L2:16"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "value"
|
||||
},
|
||||
"type": {
|
||||
"name": "float"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L2:19",
|
||||
"to": "L2:20"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 2
|
||||
},
|
||||
"type": {
|
||||
"name": "int"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L2:11",
|
||||
"to": "L2:20"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "BinaryExpr",
|
||||
"left": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "value"
|
||||
},
|
||||
"operator": "*",
|
||||
"right": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 2
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"name": "float"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L6:11",
|
||||
"to": "L6:15"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "bool"
|
||||
},
|
||||
"type": {
|
||||
"pos_args": [
|
||||
{
|
||||
"pos": 0,
|
||||
"name": "object",
|
||||
"type": {},
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"args": [],
|
||||
"kw_args": [],
|
||||
"returns": {
|
||||
"name": "bool"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L6:16",
|
||||
"to": "L6:21"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "value"
|
||||
},
|
||||
"type": {
|
||||
"name": "int"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L6:24",
|
||||
"to": "L6:25"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 2
|
||||
},
|
||||
"type": {
|
||||
"name": "int"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L6:16",
|
||||
"to": "L6:25"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "BinaryExpr",
|
||||
"left": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "value"
|
||||
},
|
||||
"operator": "%",
|
||||
"right": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 2
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"name": "int"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L6:11",
|
||||
"to": "L6:26"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "CallExpr",
|
||||
"callee": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "bool"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"_type": "BinaryExpr",
|
||||
"left": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "value"
|
||||
},
|
||||
"operator": "%",
|
||||
"right": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"keywords": {}
|
||||
},
|
||||
"type": {
|
||||
"name": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L9:23",
|
||||
"to": "L9:26"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 0.2
|
||||
},
|
||||
"type": {
|
||||
"name": "float"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L9:28",
|
||||
"to": "L9:31"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 0.5
|
||||
},
|
||||
"type": {
|
||||
"name": "float"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L9:33",
|
||||
"to": "L9:36"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 0.1
|
||||
},
|
||||
"type": {
|
||||
"name": "float"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L9:38",
|
||||
"to": "L9:41"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 1.2
|
||||
},
|
||||
"type": {
|
||||
"name": "float"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L9:22",
|
||||
"to": "L9:42"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "ListExpr",
|
||||
"items": [
|
||||
{
|
||||
"_type": "LiteralExpr",
|
||||
"value": 0.2
|
||||
},
|
||||
{
|
||||
"_type": "LiteralExpr",
|
||||
"value": 0.5
|
||||
},
|
||||
{
|
||||
"_type": "LiteralExpr",
|
||||
"value": 0.1
|
||||
},
|
||||
{
|
||||
"_type": "LiteralExpr",
|
||||
"value": 1.2
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"name": "list",
|
||||
"args": [
|
||||
{
|
||||
"name": "float"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"name": "list"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L10:19",
|
||||
"to": "L10:20"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 1
|
||||
},
|
||||
"type": {
|
||||
"name": "int"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L10:22",
|
||||
"to": "L10:23"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 2
|
||||
},
|
||||
"type": {
|
||||
"name": "int"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L10:25",
|
||||
"to": "L10:26"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 6
|
||||
},
|
||||
"type": {
|
||||
"name": "int"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L10:29",
|
||||
"to": "L10:30"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 3
|
||||
},
|
||||
"type": {
|
||||
"name": "int"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L10:28",
|
||||
"to": "L10:30"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "UnaryExpr",
|
||||
"operator": "-",
|
||||
"right": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 3
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"name": "int"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L10:18",
|
||||
"to": "L10:31"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "ListExpr",
|
||||
"items": [
|
||||
{
|
||||
"_type": "LiteralExpr",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"_type": "LiteralExpr",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"_type": "LiteralExpr",
|
||||
"value": 6
|
||||
},
|
||||
{
|
||||
"_type": "UnaryExpr",
|
||||
"operator": "-",
|
||||
"right": {
|
||||
"_type": "LiteralExpr",
|
||||
"value": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"name": "list",
|
||||
"args": [
|
||||
{
|
||||
"name": "int"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"name": "list"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L12:17",
|
||||
"to": "L12:20"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "map"
|
||||
},
|
||||
"type": {
|
||||
"name": "map",
|
||||
"params": [
|
||||
{
|
||||
"name": "T",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
},
|
||||
{
|
||||
"name": "U",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"pos_args": [
|
||||
{
|
||||
"pos": 0,
|
||||
"name": "transform",
|
||||
"type": {
|
||||
"pos_args": [
|
||||
{
|
||||
"pos": 0,
|
||||
"name": "v",
|
||||
"type": {
|
||||
"name": "T",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"args": [],
|
||||
"kw_args": [],
|
||||
"returns": {
|
||||
"name": "U",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"pos": 1,
|
||||
"name": "iterable",
|
||||
"type": {
|
||||
"name": "list",
|
||||
"args": [
|
||||
{
|
||||
"name": "T",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"name": "list"
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"args": [],
|
||||
"kw_args": [],
|
||||
"returns": {
|
||||
"name": "list",
|
||||
"args": [
|
||||
{
|
||||
"name": "U",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"name": "list"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L12:21",
|
||||
"to": "L12:27"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "double"
|
||||
},
|
||||
"type": {
|
||||
"pos_args": [],
|
||||
"args": [
|
||||
{
|
||||
"pos": 0,
|
||||
"name": "value",
|
||||
"type": {
|
||||
"name": "float"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"kw_args": [],
|
||||
"returns": {
|
||||
"name": "float"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L12:29",
|
||||
"to": "L12:35"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "floats"
|
||||
},
|
||||
"type": {
|
||||
"name": "list",
|
||||
"args": [
|
||||
{
|
||||
"name": "float"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"name": "list"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L12:17",
|
||||
"to": "L12:36"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "CallExpr",
|
||||
"callee": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "map"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"_type": "VariableExpr",
|
||||
"name": "double"
|
||||
},
|
||||
{
|
||||
"_type": "VariableExpr",
|
||||
"name": "floats"
|
||||
}
|
||||
],
|
||||
"keywords": {}
|
||||
},
|
||||
"type": {
|
||||
"name": "list",
|
||||
"args": [
|
||||
{
|
||||
"name": "float"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"name": "list"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L13:15",
|
||||
"to": "L13:18"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "map"
|
||||
},
|
||||
"type": {
|
||||
"name": "map",
|
||||
"params": [
|
||||
{
|
||||
"name": "T",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
},
|
||||
{
|
||||
"name": "U",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"pos_args": [
|
||||
{
|
||||
"pos": 0,
|
||||
"name": "transform",
|
||||
"type": {
|
||||
"pos_args": [
|
||||
{
|
||||
"pos": 0,
|
||||
"name": "v",
|
||||
"type": {
|
||||
"name": "T",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"args": [],
|
||||
"kw_args": [],
|
||||
"returns": {
|
||||
"name": "U",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"pos": 1,
|
||||
"name": "iterable",
|
||||
"type": {
|
||||
"name": "list",
|
||||
"args": [
|
||||
{
|
||||
"name": "T",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"name": "list"
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"args": [],
|
||||
"kw_args": [],
|
||||
"returns": {
|
||||
"name": "list",
|
||||
"args": [
|
||||
{
|
||||
"name": "U",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"name": "list"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L13:19",
|
||||
"to": "L13:25"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "double"
|
||||
},
|
||||
"type": {
|
||||
"pos_args": [],
|
||||
"args": [
|
||||
{
|
||||
"pos": 0,
|
||||
"name": "value",
|
||||
"type": {
|
||||
"name": "float"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"kw_args": [],
|
||||
"returns": {
|
||||
"name": "float"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L13:27",
|
||||
"to": "L13:31"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "ints"
|
||||
},
|
||||
"type": {
|
||||
"name": "list",
|
||||
"args": [
|
||||
{
|
||||
"name": "int"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"name": "list"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L13:15",
|
||||
"to": "L13:32"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "CallExpr",
|
||||
"callee": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "map"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"_type": "VariableExpr",
|
||||
"name": "double"
|
||||
},
|
||||
{
|
||||
"_type": "VariableExpr",
|
||||
"name": "ints"
|
||||
}
|
||||
],
|
||||
"keywords": {}
|
||||
},
|
||||
"type": {}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L14:11",
|
||||
"to": "L14:14"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "map"
|
||||
},
|
||||
"type": {
|
||||
"name": "map",
|
||||
"params": [
|
||||
{
|
||||
"name": "T",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
},
|
||||
{
|
||||
"name": "U",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"pos_args": [
|
||||
{
|
||||
"pos": 0,
|
||||
"name": "transform",
|
||||
"type": {
|
||||
"pos_args": [
|
||||
{
|
||||
"pos": 0,
|
||||
"name": "v",
|
||||
"type": {
|
||||
"name": "T",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"args": [],
|
||||
"kw_args": [],
|
||||
"returns": {
|
||||
"name": "U",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"pos": 1,
|
||||
"name": "iterable",
|
||||
"type": {
|
||||
"name": "list",
|
||||
"args": [
|
||||
{
|
||||
"name": "T",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"name": "list"
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"args": [],
|
||||
"kw_args": [],
|
||||
"returns": {
|
||||
"name": "list",
|
||||
"args": [
|
||||
{
|
||||
"name": "U",
|
||||
"bound": null,
|
||||
"variance": "INVARIANT"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"name": "list"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L14:15",
|
||||
"to": "L14:21"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "is_odd"
|
||||
},
|
||||
"type": {
|
||||
"pos_args": [],
|
||||
"args": [
|
||||
{
|
||||
"pos": 0,
|
||||
"name": "value",
|
||||
"type": {
|
||||
"name": "int"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"kw_args": [],
|
||||
"returns": {
|
||||
"name": "bool"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L14:23",
|
||||
"to": "L14:27"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "ints"
|
||||
},
|
||||
"type": {
|
||||
"name": "list",
|
||||
"args": [
|
||||
{
|
||||
"name": "int"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"name": "list"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"from": "L14:11",
|
||||
"to": "L14:28"
|
||||
},
|
||||
"expr": {
|
||||
"_type": "CallExpr",
|
||||
"callee": {
|
||||
"_type": "VariableExpr",
|
||||
"name": "map"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"_type": "VariableExpr",
|
||||
"name": "is_odd"
|
||||
},
|
||||
{
|
||||
"_type": "VariableExpr",
|
||||
"name": "ints"
|
||||
}
|
||||
],
|
||||
"keywords": {}
|
||||
},
|
||||
"type": {
|
||||
"name": "list",
|
||||
"args": [
|
||||
{
|
||||
"name": "bool"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"name": "list"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -7,68 +7,14 @@ Module(
|
||||
alias(name='Meter'),
|
||||
alias(name='Second')],
|
||||
level=0),
|
||||
Assign(
|
||||
targets=[
|
||||
Name(id='__midas_a0__')],
|
||||
value=Constant(value=123.45)),
|
||||
Assert(
|
||||
test=Call(
|
||||
func=Name(id='isinstance'),
|
||||
args=[
|
||||
Name(id='__midas_a0__'),
|
||||
Name(id='float')],
|
||||
keywords=[]),
|
||||
msg=JoinedStr(
|
||||
values=[
|
||||
Constant(value='01_simple_types.py:L3:19: CastError: Cannot cast '),
|
||||
FormattedValue(
|
||||
value=Attribute(
|
||||
value=Call(
|
||||
func=Name(id='type'),
|
||||
args=[
|
||||
Name(id='__midas_a0__')],
|
||||
keywords=[]),
|
||||
attr='__name__'),
|
||||
conversion=-1),
|
||||
Constant(value=' to float')])),
|
||||
Assign(
|
||||
targets=[
|
||||
Name(id='distance')],
|
||||
value=Name(id='__midas_a0__')),
|
||||
Delete(
|
||||
targets=[
|
||||
Name(id='__midas_a0__')]),
|
||||
Assign(
|
||||
targets=[
|
||||
Name(id='__midas_a1__')],
|
||||
value=Constant(value=6.7)),
|
||||
Assert(
|
||||
test=Call(
|
||||
func=Name(id='isinstance'),
|
||||
args=[
|
||||
Name(id='__midas_a1__'),
|
||||
Name(id='float')],
|
||||
keywords=[]),
|
||||
msg=JoinedStr(
|
||||
values=[
|
||||
Constant(value='01_simple_types.py:L4:16: CastError: Cannot cast '),
|
||||
FormattedValue(
|
||||
value=Attribute(
|
||||
value=Call(
|
||||
func=Name(id='type'),
|
||||
args=[
|
||||
Name(id='__midas_a1__')],
|
||||
keywords=[]),
|
||||
attr='__name__'),
|
||||
conversion=-1),
|
||||
Constant(value=' to float')])),
|
||||
value=Constant(value=123.45)),
|
||||
Assign(
|
||||
targets=[
|
||||
Name(id='time')],
|
||||
value=Name(id='__midas_a1__')),
|
||||
Delete(
|
||||
targets=[
|
||||
Name(id='__midas_a1__')]),
|
||||
value=Constant(value=6.7)),
|
||||
Assign(
|
||||
targets=[
|
||||
Name(id='speed')],
|
||||
|
||||
@@ -263,6 +263,7 @@ class PythonAstJsonSerializer(
|
||||
"_type": "CastExpr",
|
||||
"type": expr.type.accept(self),
|
||||
"expr": expr.expr.accept(self),
|
||||
"unsafe": expr.unsafe,
|
||||
}
|
||||
|
||||
def visit_ternary_expr(self, expr: TernaryExpr) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user