feat(checker): type check dictionaries
This commit is contained in:
@@ -150,3 +150,32 @@ extend list[T] {
|
||||
|
||||
prop __doc__: str
|
||||
}
|
||||
|
||||
extend dict[K, V] {
|
||||
def copy: fn() -> dict[K, V]
|
||||
def keys: fn() -> list[K] // TODO: use builtin types
|
||||
def values: fn() -> list[V] // TODO: use builtin types
|
||||
// def items: fn() -> list[tuple[K, V]] // TODO: use builtin types
|
||||
|
||||
// def get: fn(key: K, default: None = None, /) -> V | None
|
||||
def get: fn(key: K, default: V, /) -> V
|
||||
// def get: fn[T](key: K, default: T, /) -> V | T
|
||||
def pop: fn(key: K, /) -> V
|
||||
def pop: fn(key: K, default: V, /) -> V
|
||||
// def pop: fn[T](key: K, default: T, /) -> V | T
|
||||
def __len__: fn() -> int
|
||||
def __getitem__: fn(key: K, /) -> V
|
||||
def __setitem__: fn(key: K, value: V, /) -> None
|
||||
def __delitem__: fn(key: K, /) -> None
|
||||
// def __iter__: fn() -> Iterator[K]
|
||||
def __eq__: fn(value: object, /) -> bool
|
||||
// def __reversed__: fn() -> Iterator[K]
|
||||
|
||||
def __or__: fn(value: dict[K, V], /) -> dict[K, V]
|
||||
// def __or__: fn[K2, V2](value: dict[K2, V2], /) -> dict[K | K2, V | V2]
|
||||
def __ror__: fn(value: dict[K, V], /) -> dict[K, V]
|
||||
// def __ror__: fn[K2, V2](value: dict[K2, V2], /) -> dict[K | K2, V | V2]
|
||||
// def __ior__: fn(value: SupportsKeysAndGetItem[K, V], /) -> dict[K, V]
|
||||
// def __ior__: fn(value: Iterable[tuple[K, V]], /) -> dict[K, V]
|
||||
|
||||
}
|
||||
@@ -39,3 +39,14 @@ def define_builtins(reg: TypesRegistry):
|
||||
body=BaseType(name="list"),
|
||||
),
|
||||
)
|
||||
dict = reg.define_type(
|
||||
"dict",
|
||||
GenericType(
|
||||
name="dict",
|
||||
params=[
|
||||
TypeVar(name="K", bound=None),
|
||||
TypeVar(name="V", bound=None),
|
||||
],
|
||||
body=BaseType(name="dict"),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -552,6 +552,46 @@ class PythonTyper(
|
||||
)
|
||||
return self.types.apply_generic(list_type, [UnknownType()])
|
||||
|
||||
def visit_dict_expr(self, expr: p.DictExpr) -> Type:
|
||||
dict_type: Type = self.types.get_type("dict")
|
||||
|
||||
key_types: list[Type] = []
|
||||
value_types: list[Type] = []
|
||||
for key, value in zip(expr.keys, expr.values):
|
||||
if key is None:
|
||||
self.reporter.warning(
|
||||
value.location, "Dictionary unpacking not supported"
|
||||
)
|
||||
continue
|
||||
key_types.append(self.type_of(key))
|
||||
value_types.append(self.type_of(value))
|
||||
|
||||
key_types = self.types.reduce_types(key_types)
|
||||
value_types = self.types.reduce_types(value_types)
|
||||
|
||||
if len(key_types) == 0 or len(value_types) == 0:
|
||||
return dict_type
|
||||
|
||||
key_type: Type = UnknownType()
|
||||
value_type: Type = UnknownType()
|
||||
|
||||
if len(key_types) == 1:
|
||||
key_type = key_types[0]
|
||||
else:
|
||||
self.reporter.error(
|
||||
expr.location,
|
||||
f"Heterogeneous dict keys: {key_types}",
|
||||
)
|
||||
|
||||
if len(value_types) == 1:
|
||||
value_type = value_types[0]
|
||||
else:
|
||||
self.reporter.error(
|
||||
expr.location,
|
||||
f"Heterogeneous dict values: {value_types}",
|
||||
)
|
||||
return self.types.apply_generic(dict_type, [key_type, value_type])
|
||||
|
||||
def visit_subscript_expr(self, expr: p.SubscriptExpr) -> Type:
|
||||
object: Type = self.type_of(expr.object)
|
||||
operation: Optional[Type] = self.types.lookup_member(object, "__getitem__")
|
||||
|
||||
@@ -213,6 +213,13 @@ class Resolver(p.Stmt.Visitor[None], p.Expr.Visitor[None]):
|
||||
for item in expr.items:
|
||||
self.resolve(item)
|
||||
|
||||
def visit_dict_expr(self, expr: p.DictExpr) -> None:
|
||||
for key in expr.keys:
|
||||
if key is not None:
|
||||
self.resolve(key)
|
||||
for value in expr.values:
|
||||
self.resolve(value)
|
||||
|
||||
def visit_subscript_expr(self, expr: p.SubscriptExpr) -> None:
|
||||
self.resolve(expr.object)
|
||||
self.resolve(expr.index)
|
||||
|
||||
@@ -4,8 +4,8 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from midas.ast.location import Location
|
||||
import midas.ast.python as p
|
||||
from midas.ast.location import Location
|
||||
from midas.checker.types import (
|
||||
AliasType,
|
||||
AppliedType,
|
||||
@@ -139,6 +139,12 @@ class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
|
||||
elts=[item.accept(self) for item in expr.items],
|
||||
)
|
||||
|
||||
def visit_dict_expr(self, expr: p.DictExpr) -> ast.expr:
|
||||
return ast.Dict(
|
||||
keys=[key.accept(self) if key is not None else None for key in expr.keys],
|
||||
values=[value.accept(self) for value in expr.values],
|
||||
)
|
||||
|
||||
def visit_subscript_expr(self, expr: p.SubscriptExpr) -> ast.expr:
|
||||
return ast.Subscript(
|
||||
value=expr.object.accept(self),
|
||||
|
||||
Reference in New Issue
Block a user