Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

more mypy #125

Merged
merged 61 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
f6dd31b
start checking mypy and work on some hints
altendky Nov 25, 2021
ac4d446
remove unused imports
altendky Nov 25, 2021
6706e20
some more mypy
altendky Nov 26, 2021
ea40055
more
altendky Jul 10, 2022
bf598ff
fixup
altendky Aug 18, 2022
2293297
little
altendky Aug 18, 2022
271e80a
Merge branch 'main' into mypie
altendky Nov 13, 2023
3a5e0b0
catch up on test hints
altendky Nov 13, 2023
0f3f7a7
more
altendky Nov 13, 2023
78d02f5
fixup
altendky Nov 13, 2023
89b1cfb
more
altendky Nov 13, 2023
5af95bc
Merge branch 'main' into mypie
altendky Nov 14, 2023
45a33a4
py.typed
altendky Nov 21, 2023
a542909
ignores...
altendky Nov 21, 2023
6abbfe9
stuff
altendky Nov 21, 2023
1f51f77
just ignore for now
altendky Nov 21, 2023
c9feb37
note
altendky Nov 21, 2023
ec21d41
Merge branch 'main' into mypie
altendky Dec 6, 2023
a93ce62
tidy
altendky Dec 7, 2023
a377a48
fix
altendky Dec 7, 2023
baeb9df
fix
altendky Dec 7, 2023
01dac30
flake8
altendky Dec 7, 2023
12f2e2e
typing_externsions.TypeGuard
altendky Dec 7, 2023
ba0ea9a
Apply suggestions from code review
altendky Dec 7, 2023
b5d6e23
add newly needed ignore
altendky Dec 7, 2023
7dee522
fix NestedTupleOfBytes
altendky Dec 7, 2023
2d125bb
Merge branch 'main' into mypie
altendky Dec 9, 2023
302813a
`CLVMObjectLike` -> `CLVMStorage`
altendky Dec 9, 2023
502e5e1
tidy hinting in `core_ops`
altendky Dec 9, 2023
9422b0c
unused assignment in tests
altendky Dec 9, 2023
29926ba
typing_extensions.Never
altendky Dec 9, 2023
12571a4
tidy
altendky Dec 10, 2023
90fb89c
tidy
altendky Dec 10, 2023
55a514d
more tidy
altendky Dec 14, 2023
142ecd1
oops
altendky Dec 14, 2023
ae67139
update note
altendky Dec 14, 2023
c76e7d9
shift around
altendky Dec 14, 2023
393e1ba
more
altendky Dec 14, 2023
d77164d
undo
altendky Dec 14, 2023
c3214f6
tidy
altendky Dec 14, 2023
271ff39
tidy
altendky Dec 14, 2023
6bd0720
tidy
altendky Dec 14, 2023
0b30a6a
prepare for more strict mypy
altendky Dec 14, 2023
1a43f5f
enable 'free' mypy constraints
altendky Dec 14, 2023
1f9dccd
no_implicit_reexport
altendky Dec 14, 2023
bdbb737
some untyped generics
altendky Dec 15, 2023
e66b640
almost, but not quite
altendky Dec 15, 2023
8027443
ToCLVMStorage
richardkiss Dec 18, 2023
98e1f9c
break circular import
altendky Dec 19, 2023
8369c45
touchup
altendky Dec 21, 2023
daabb3e
Merge branch 'main' into mypie
altendky Mar 15, 2024
7222f09
future
altendky Mar 19, 2024
23e47d8
less future
altendky Mar 19, 2024
8f05894
`SExp.to(Any)`
altendky Mar 21, 2024
15f4c2e
remove ignores
altendky Mar 22, 2024
f806e68
use `PythonReturnType` for as python return type
altendky Mar 22, 2024
1f3f89b
drop any
altendky Mar 22, 2024
bfe9e84
hmm
altendky Mar 22, 2024
44ca118
Revert "hmm"
altendky Mar 25, 2024
8f7c410
Revert "drop any"
altendky Mar 25, 2024
7a956fc
Revert "use `PythonReturnType` for as python return type"
altendky Mar 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ jobs:
python -m pip install flake8
- name: flake8
run: flake8 clvm tests --max-line-length=120
- name: mypy
run: mypy
- name: Test with pytest
run: |
py.test tests
Expand Down
36 changes: 32 additions & 4 deletions clvm/CLVMObject.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
from __future__ import annotations

import typing

from typing_extensions import Protocol


class CLVMObjectLike(Protocol):
altendky marked this conversation as resolved.
Show resolved Hide resolved
# It's not clear if it is possible to express the exclusivity without maybe
# restructuring all the classes.
# TODO: can we make these read only? nope, to_sexp_type() depends on mutation
atom: typing.Optional[bytes]
pair: typing.Optional[PairType]
altendky marked this conversation as resolved.
Show resolved Hide resolved


PairType = typing.Tuple[CLVMObjectLike, CLVMObjectLike]


_T_CLVMObject = typing.TypeVar("_T_CLVMObject", bound="CLVMObject")


class CLVMObject:
"""
Expand All @@ -11,19 +29,29 @@ class CLVMObject:

# this is always a 2-tuple of an object implementing the CLVM object
# protocol.
pair: typing.Optional[typing.Tuple[typing.Any, typing.Any]]
pair: typing.Optional[PairType]
__slots__ = ["atom", "pair"]

def __new__(class_, v):
if isinstance(v, CLVMObject):
@staticmethod
def __new__(
class_: typing.Type[_T_CLVMObject],
# TODO: which? review?
# v: typing.Union[CLVMObject, CLVMObjectLike, typing.Tuple[CLVMObject, CLVMObject], bytes],
v: typing.Union[CLVMObject, bytes, PairType],
) -> _T_CLVMObject:
if isinstance(v, class_):
return v
self = super(CLVMObject, class_).__new__(class_)
if isinstance(v, tuple):
if len(v) != 2:
raise ValueError("tuples must be of size 2, cannot create CLVMObject from: %s" % str(v))
self.pair = v
self.atom = None
# TODO: discussing this
# elif isinstance(v, bytes):
else:
self.atom = v
self.atom = v # type: ignore[assignment]
self.pair = None
# else:
# raise ValueError(f"cannot create CLVMObject from: {v!r}")
return self
10 changes: 9 additions & 1 deletion clvm/EvalError.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from clvm.SExp import SExp


class EvalError(Exception):
def __init__(self, message: str, sexp):
def __init__(self, message: str, sexp: SExp) -> None:
super().__init__(message)
self._sexp = sexp
151 changes: 91 additions & 60 deletions clvm/SExp.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@


from .as_python import as_python
from .CLVMObject import CLVMObject
from .CLVMObject import CLVMObject, CLVMObjectLike

from .EvalError import EvalError

Expand All @@ -16,27 +16,29 @@

CastableType = typing.Union[
"SExp",
"CLVMObject",
CLVMObjectLike,
bytes,
str,
int,
None,
list,
typing.Tuple[typing.Any, typing.Any],
typing.Tuple["CastableType", ...],
altendky marked this conversation as resolved.
Show resolved Hide resolved
]


NULL = b""


def looks_like_clvm_object(o: typing.Any) -> bool:
# TODO: can this be replaced by a runtime protocol check?
def looks_like_clvm_object(o: typing.Any) -> typing.TypeGuard[CLVMObjectLike]:
d = dir(o)
return "atom" in d and "pair" in d


# this function recognizes some common types and turns them into plain bytes,
def convert_atom_to_bytes(
v: typing.Union[bytes, str, int, None, list],
# TODO: not any list, but an empty list
v: typing.Union[bytes, str, int, None, typing.List, typing.SupportsBytes],
altendky marked this conversation as resolved.
Show resolved Hide resolved
) -> bytes:

if isinstance(v, bytes):
Expand All @@ -55,24 +57,35 @@ def convert_atom_to_bytes(
raise ValueError("can't cast %s (%s) to bytes" % (type(v), v))


StackValType = typing.Union[CLVMObjectLike, typing.Tuple[CLVMObjectLike, CLVMObjectLike]]
StackType = typing.List[typing.Union[StackValType, "StackType"]]

# returns a clvm-object like object
def to_sexp_type(
v: CastableType,
):
stack = [v]
ops = [(0, None)] # convert
v: CLVMObjectLike,
) -> CLVMObjectLike:
stack: StackType = [v]
ops: typing.List[typing.Union[typing.Tuple[typing.Literal[0], None], typing.Tuple[int, int]]] = [(0, None)] # convert

# TODO: can this type just be applied to the parameter instead?
internal_v: typing.Union[CLVMObjectLike, typing.Tuple, typing.List]
target: int
element: CLVMObjectLike

while len(ops) > 0:
op, target = ops.pop()
# convert value
if op == 0:
op_target = ops.pop()
# convert valuefrom .operators import OperatorDict

# this form allows mypy to follow the not-none-ness of op_target[1] for all other operations
if op_target[1] is None:
assert op_target[0] == 0
if looks_like_clvm_object(stack[-1]):
continue
v = stack.pop()
if isinstance(v, tuple):
if len(v) != 2:
raise ValueError("can't cast tuple of size %d" % len(v))
left, right = v
internal_v = stack.pop()
if isinstance(internal_v, tuple):
if len(internal_v) != 2:
raise ValueError("can't cast tuple of size %d" % len(internal_v))
left, right = internal_v
target = len(stack)
stack.append(CLVMObject((left, right)))
if not looks_like_clvm_object(right):
Expand All @@ -83,38 +96,53 @@ def to_sexp_type(
stack.append(left)
ops.append((1, target)) # set left
ops.append((0, None)) # convert
continue
if isinstance(v, list):
elif isinstance(internal_v, list):
target = len(stack)
stack.append(CLVMObject(NULL))
for _ in v:
for _ in internal_v:
stack.append(_)
ops.append((3, target)) # prepend list
# we only need to convert if it's not already the right
# type
if not looks_like_clvm_object(_):
ops.append((0, None)) # convert
continue
stack.append(CLVMObject(convert_atom_to_bytes(v)))
continue

if op == 1: # set left
stack[target].pair = (CLVMObject(stack.pop()), stack[target].pair[1])
continue
if op == 2: # set right
stack[target].pair = (stack[target].pair[0], CLVMObject(stack.pop()))
continue
if op == 3: # prepend list
stack[target] = CLVMObject((stack.pop(), stack[target]))
continue
else:
# TODO: do we have to ignore?
stack.append(CLVMObject(convert_atom_to_bytes(internal_v))) # type: ignore[arg-type]
elif op_target[0] == 1: # set left
target = op_target[1]
# TODO: do we have to ignore?
element = stack[target] # type: ignore[assignment]
pair = element.pair
assert pair is not None
# TODO: do we have to ignore?
element.pair = (CLVMObject(stack.pop()), pair[1]) # type: ignore[arg-type]
elif op_target[0] == 2: # set right
target = op_target[1]
# TODO: do we have to ignore?
element = stack[target] # type: ignore[assignment]
pair = element.pair
assert pair is not None
# TODO: do we have to ignore?
element.pair = (pair[0], CLVMObject(stack.pop())) # type: ignore[arg-type]
elif op_target[0] == 3: # prepend list
target = op_target[1]
# TODO: do we have to ignore?
stack[target] = CLVMObject((stack.pop(), stack[target])) # type: ignore[arg-type]
# TODO: what about an else to fail explicitly on an unknown op?
# there's exactly one item left at this point
if len(stack) != 1:
raise ValueError("internal error")

# stack[0] implements the clvm object protocol and can be wrapped by an SExp
return stack[0]
# TODO: do we have to ignore?
return stack[0] # type: ignore[return-value]


_T_SExp = typing.TypeVar("_T_SExp", bound="SExp")


# TODO: Maybe there is some way to track atom vs. pair SExps to help hinting out a bit
class SExp:
"""
SExp provides higher level API on top of any object implementing the CLVM
Expand All @@ -129,86 +157,89 @@ class SExp:
elements implementing the CLVM object protocol.
Exactly one of "atom" and "pair" must be None.
"""
true: "SExp"
false: "SExp"
__null__: "SExp"
true: typing.ClassVar["SExp"]
false: typing.ClassVar["SExp"]
__null__: typing.ClassVar["SExp"]

# the underlying object implementing the clvm object protocol
atom: typing.Optional[bytes]

# this is a tuple of the otherlying CLVMObject-like objects. i.e. not
# SExp objects with higher level functions, or None
pair: typing.Optional[typing.Tuple[typing.Any, typing.Any]]
pair: typing.Optional[typing.Tuple[CLVMObjectLike, CLVMObjectLike]]

def __init__(self, obj):
def __init__(self, obj: CLVMObjectLike) -> None:
self.atom = obj.atom
self.pair = obj.pair

# this returns a tuple of two SExp objects, or None
def as_pair(self) -> typing.Tuple["SExp", "SExp"]:
def as_pair(self) -> typing.Optional[typing.Tuple["SExp", "SExp"]]:
pair = self.pair
if pair is None:
return pair
return (self.__class__(pair[0]), self.__class__(pair[1]))

# TODO: deprecate this. Same as .atom property
def as_atom(self):
def as_atom(self) -> typing.Optional[bytes]:
return self.atom

def listp(self):
def listp(self) -> bool:
return self.pair is not None

def nullp(self):
def nullp(self) -> bool:
v = self.atom
return v is not None and len(v) == 0

def as_int(self):
def as_int(self) -> int:
if self.atom is None:
raise TypeError("Unable to convert a pair to an int")
return int_from_bytes(self.atom)

def as_bin(self):
def as_bin(self) -> bytes:
f = io.BytesIO()
sexp_to_stream(self, f)
return f.getvalue()

@classmethod
def to(class_, v: CastableType) -> "SExp":
if isinstance(v, class_):
def to(cls: typing.Type[_T_SExp], v: CastableType) -> _T_SExp:
if isinstance(v, cls):
return v

if looks_like_clvm_object(v):
return class_(v)
return cls(v)

# this will lazily convert elements
return class_(to_sexp_type(v))
# TODO: do we have to ignore?
return cls(to_sexp_type(v)) # type: ignore[arg-type]

def cons(self, right):
def cons(self: _T_SExp, right: _T_SExp) -> _T_SExp:
return self.to((self, right))

def first(self):
def first(self: _T_SExp) -> _T_SExp:
pair = self.pair
if pair:
return self.__class__(pair[0])
raise EvalError("first of non-cons", self)

def rest(self):
def rest(self: _T_SExp) -> _T_SExp:
pair = self.pair
if pair:
return self.__class__(pair[1])
raise EvalError("rest of non-cons", self)

@classmethod
def null(class_):
def null(class_) -> "SExp":
return class_.__null__

def as_iter(self):
def as_iter(self: _T_SExp) -> typing.Iterable[_T_SExp]:
v = self
while not v.nullp():
yield v.first()
v = v.rest()

def __eq__(self, other: CastableType):
def __eq__(self, other: object) -> bool:
try:
other = self.to(other)
other = self.to(typing.cast(CastableType, other))
to_compare_stack = [(self, other)]
while to_compare_stack:
s1, s2 = to_compare_stack.pop()
Expand All @@ -226,21 +257,21 @@ def __eq__(self, other: CastableType):
except ValueError:
return False

def list_len(self):
def list_len(self) -> int:
v = self
size = 0
while v.listp():
size += 1
v = v.rest()
return size

def as_python(self):
def as_python(self) -> typing.Any:
altendky marked this conversation as resolved.
Show resolved Hide resolved
return as_python(self)

def __str__(self):
def __str__(self) -> str:
return self.as_bin().hex()

def __repr__(self):
def __repr__(self) -> str:
return "%s(%s)" % (self.__class__.__name__, str(self))


Expand Down
Loading
Loading