Skip to content

Commit

Permalink
cache: streamline the disassemble functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
delfick committed Oct 8, 2023
1 parent a5c9da3 commit 817bdea
Show file tree
Hide file tree
Showing 25 changed files with 551 additions and 539 deletions.
8 changes: 8 additions & 0 deletions docs/strcs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
Changelog
---------

.. _release-0.4.0:

0.4.0 - TBD
* Add a ``disassemble`` method to the type cache and implemented ``disassemble``
on ``strcs.Type`` using it. Note that the signature also changes to no
longer have an "expect" but also it's smarter about the resulting Type
it returns.

.. _release-0.3.0:

0.3.0 - 17 September 2023
Expand Down
3 changes: 3 additions & 0 deletions docs/strcs/features/disassembled.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ Types
:member-order: bysource

.. autoclass:: strcs.TypeCache
:members: disassemble

.. autoprotocol:: strcs.disassemble.Disassembler

.. autoclass:: strcs.MRO
:members:
Expand Down
2 changes: 1 addition & 1 deletion strcs/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ def adjusted_meta(self, meta: Meta, typ: Type[T], type_cache: TypeCache) -> Meta
clone = meta.clone()
for field in attrs.fields(self.meta.__class__): # type:ignore
if not field.name.startswith("_"):
optional = Type.create(field.type, cache=type_cache).optional
optional = type_cache.disassemble(field.type).optional
val = getattr(self.meta, field.name)
if not optional or val is not None:
clone[field.name] = val
Expand Down
4 changes: 2 additions & 2 deletions strcs/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,11 @@ def deal(res: ConvertResponse[T], value: object) -> T:
return tp.cast(T, value)
else:
if not isinstance(res, Mapping) and issubclass(
want.checkable, Type.create(type(res), cache=self.type_cache).checkable
want.checkable, self.type_cache.disassemble(type(res)).checkable
):
raise errors.SupertypeNotValid(
want=want.checkable,
got=Type.create(type(res), cache=self.type_cache).checkable,
got=self.type_cache.disassemble(type(res)).checkable,
reason="A Super type is not a valid value to convert",
)

Expand Down
3 changes: 2 additions & 1 deletion strcs/disassemble/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ._base import Type
from ._base import Disassembler, Type
from ._cache import TypeCache
from ._creation import fill, instantiate
from ._extract import IsAnnotated, extract_annotation, extract_optional
Expand All @@ -15,6 +15,7 @@
from ._type_tree import MRO, HasOrigBases

__all__ = [
"Disassembler",
"Type",
"TypeCache",
"IsAnnotated",
Expand Down
56 changes: 47 additions & 9 deletions strcs/disassemble/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class Type(tp.Generic[T]):
typ = strcs.Type.create(int, cache=type_cache)
typ2 = strcs.Type.create(int | None, cache=type_cache)
typ3 = type_cache.disassemble(int | None)
typ4 = typ.disassemble(int | None)
...
"""
Expand Down Expand Up @@ -77,6 +79,15 @@ class Missing(MissingType):
init=False, factory=lambda: {}, repr=False, order=False, hash=False
)

disassemble: "Disassembler" = attrs.field(
init=False,
default=attrs.Factory(lambda s: s.cache.disassemble, takes_self=True),
repr=False,
order=False,
hash=False,
)
"""Object for creating new Type classes without having to pass around the type cache"""

original: object
"The original object being wrapped"

Expand Down Expand Up @@ -275,13 +286,6 @@ def __gte__(self, other: object) -> bool:

return self.score >= other.score

def disassemble(self, expect: type[U], typ: object) -> "Type[U]":
"""
Return a new :class:`strcs.Type` for the provided object using the
type cache on this instance.
"""
return Type.create(typ, expect=expect, cache=self.cache)

def reassemble(
self, resolved: object, *, with_annotation: bool = True, with_optional: bool = True
) -> object:
Expand Down Expand Up @@ -394,7 +398,7 @@ def nonoptional_union_types(self) -> tuple["Type", ...]:
for origin in origins:
if origin is None:
continue
ds.append(self.disassemble(object, origin))
ds.append(self.disassemble(origin))

union = tuple(sorted(ds, key=lambda d: d.score, reverse=True))

Expand Down Expand Up @@ -546,7 +550,7 @@ def is_equivalent_type_for(self, value: object) -> tp.TypeGuard[T]:
if isinstance(value, type):
subclass_of = value
else:
subclass_of = self.disassemble(object, type(value)).checkable
subclass_of = self.disassemble(type(value)).checkable
return issubclass(subclass_of, self.checkable)

@memoized_property
Expand Down Expand Up @@ -659,3 +663,37 @@ def checkable(self) -> type[InstanceCheck]:
This is memoized.
"""
return create_checkable(self)


class Disassembler(tp.Protocol):
"""
Used to disassemble some type using an existing type cache
"""

type_cache: "TypeCache"

@tp.overload
def __call__(self, typ: type[U]) -> "Type[U]":
...

@tp.overload
def __call__(self, typ: Type[U]) -> "Type[U]":
...

@tp.overload
def __call__(self, typ: object) -> "Type[object]":
...

def __call__(self, typ: type[U] | object) -> "Type[U] | Type[object]":
"""
Used to disassemble some type using an existing type cache
Pass in expect to alter the type that the static type checker sees
"""

def typed(self, expect: type[U], typ: object) -> "Type[U]":
"""
Return a new :class:`strcs.Type` for the provided object using this
type cache and the expected type.
"""
...
48 changes: 44 additions & 4 deletions strcs/disassemble/_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,43 @@
from collections.abc import MutableMapping

if tp.TYPE_CHECKING:
from ._base import Type
from ._base import Disassembler, Type

U = tp.TypeVar("U")


class _TypeCacheDisassembler:
def __init__(self, type_cache: "TypeCache"):
from ._base import Type

self.Type = Type
self.type_cache = type_cache

@tp.overload
def __call__(self, typ: type[U]) -> "Type[U]":
...

@tp.overload
def __call__(self, typ: "Type[U]") -> "Type[U]":
...

@tp.overload
def __call__(self, typ: object) -> "Type[object]":
...

def __call__(self, typ: type[U] | object) -> "Type[U] | Type[object]":
"""
Return a new :class:`strcs.Type` for the provided object using this
type cache
"""
return self.Type.create(typ, expect=object, cache=self.type_cache)

def typed(self, expect: type[U], typ: object) -> "Type[U]":
"""
Return a new :class:`strcs.Type` for the provided object using this
type cache and the expected type.
"""
return self.Type.create(typ, expect=expect, cache=self.type_cache)


class TypeCache(MutableMapping[object, "Type"]):
Expand All @@ -19,7 +55,7 @@ class TypeCache(MutableMapping[object, "Type"]):
type_cache = strcs.TypeCache()
typ = strcs.Type.create(int, cache=type_cache)
typ = type_cache.disassemble(int)
assert type_cache[int] is typ
assert int in type_cache
Expand All @@ -34,8 +70,12 @@ class TypeCache(MutableMapping[object, "Type"]):
type_cache.clear()
"""

def __init__(self):
self.cache = {}
disassemble: "Disassembler"
"""Used to create new Types using this type cache"""

def __init__(self) -> None:
self.cache: dict[tuple[type, object], "Type"] = {}
self.disassemble = _TypeCacheDisassembler(self)

def key(self, o: object) -> tuple[type, object]:
return (type(o), o)
Expand Down
4 changes: 2 additions & 2 deletions strcs/disassemble/_instance_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class Meta(InstanceCheck.Meta):

if tp.get_origin(Meta.extracted) in union_types:
check_against = tuple(
disassembled.disassemble(object, a).checkable for a in tp.get_args(Meta.extracted)
disassembled.disassemble(a).checkable for a in tp.get_args(Meta.extracted)
)

Meta.typ = Meta.extracted
Expand Down Expand Up @@ -303,7 +303,7 @@ def __subclasscheck__(cls, C: type) -> bool:
if not issubclass(C, check_against):
return False

want = disassembled.disassemble(object, C)
want = disassembled.disassemble(C)
for w, g in zip(want.mro.all_vars, disassembled.mro.all_vars):
if isinstance(w, Type) and isinstance(g, Type):
if not issubclass(w.checkable, g.checkable):
Expand Down
36 changes: 18 additions & 18 deletions strcs/disassemble/_type_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class MRO:
type_cache = strcs.TypeCache()
typ = strcs.Type.create(my_code.my_class, cache=type_cache)
typ = type_cache.disassemble(my_code.my_class)
mro = typ.mro
"""

Expand Down Expand Up @@ -108,7 +108,7 @@ def create(cls, start: object | None, type_cache: TypeCache) -> "MRO":
mro = () if origin is None or not hasattr(origin, "__mro__") else origin.__mro__

orig_bases = HasOrigBases.determine_orig_bases(origin, mro)
bases = [Type.create(base, expect=object, cache=type_cache) for base in orig_bases]
bases = [type_cache.disassemble(base) for base in orig_bases]

return cls(
_start=start, _origin=origin, _args=args, _mro=mro, _bases=bases, _type_cache=type_cache
Expand Down Expand Up @@ -157,7 +157,7 @@ class Two(tp.Generic[U], One[U]):
pass
type_cache = strcs.TypeCache()
typevars = strcs.Type.create(Two, cache=type_cache).mro.typevars
typevars = type_cache.disassemble(Two).mro.typevars
Will result in having:
Expand Down Expand Up @@ -242,7 +242,7 @@ def all_vars(self) -> tuple[Type | type[Type.Missing], ...]:
result: list[Type | type[Type.Missing]] = []
typevars = list(self.typevars.items())
if self.args and not typevars and self.origin not in union_types:
return tuple(Type.create(arg, cache=self.type_cache) for arg in self.args)
return tuple(self.type_cache.disassemble(arg) for arg in self.args)

found: set[tuple[type, tp.TypeVar | int]] = set()

Expand All @@ -258,7 +258,7 @@ def all_vars(self) -> tuple[Type | type[Type.Missing], ...]:

typed: Type | type[Type.Missing]
if value is not Type.Missing:
typed = Type.create(value, cache=self.type_cache)
typed = self.type_cache.disassemble(value)
else:
typed = Type.Missing

Expand Down Expand Up @@ -291,9 +291,9 @@ class Two(tp.Generic[U], One[U]):
type_cache = strcs.TypeCache()
typ1 = strcs.Type.create(One, cache=type_cache)
typ2 = strcs.Type.create(One[int], cache=type_cache)
typ3 = strcs.Type.create(Two[str], cache=type_cache)
typ1 = type_cache.disassemble(One)
typ2 = type_cache.disassemble(One[int])
typ3 = type_cache.disassemble(Two[str])
assert typ1.mro.signature_for_display == "~T"
assert typ2.mro.signature_for_display == "int"
Expand Down Expand Up @@ -321,7 +321,7 @@ class Two(tp.Generic[U], One[U]):
if value is Type.Missing:
result.append(repr(tv))
else:
result.append(Type.create(value, cache=self.type_cache).for_display())
result.append(self.type_cache.disassemble(value).for_display())

return ", ".join(result)

Expand All @@ -340,7 +340,7 @@ def raw_fields(self) -> tp.Sequence[Field]:
"""
result: list[Field] = []
for cls in reversed(self.mro):
disassembled = Type.create(cls, expect=object, cache=self.type_cache)
disassembled: Type[object] = self.type_cache.disassemble(cls)
fields = disassembled.raw_fields

for field in fields:
Expand All @@ -352,7 +352,7 @@ def raw_fields(self) -> tp.Sequence[Field]:

f.default = field.default
f.kind = field.kind
f.disassembled_type = Type.create(field.type, cache=self.type_cache)
f.disassembled_type = self.type_cache.disassemble(field.type)
f.owner = cls
found = True
break
Expand All @@ -376,15 +376,15 @@ def fields(self) -> tp.Sequence[Field]:

for field in self.raw_fields:
field_type = field.type
field_type_info = Type.create(field_type, expect=object, cache=self.type_cache)
field_type_info = self.type_cache.disassemble(field_type)

extracted = field_type_info.extracted
if isinstance(extracted, tp.TypeVar):
field_type = extracted

if isinstance(field_type, tp.TypeVar):
replacement = typevars[
(Type.create(field.original_owner, cache=self.type_cache).checkable, field_type)
(self.type_cache.disassemble(field.original_owner).checkable, field_type)
]
if isinstance(replacement, self.Referal):
replacement = replacement.value
Expand All @@ -394,7 +394,7 @@ def fields(self) -> tp.Sequence[Field]:
field_type = object

field_type = field_type_info.reassemble(field_type)
fields.append(field.with_replaced_type(Type.create(field_type, cache=self.type_cache)))
fields.append(field.with_replaced_type(self.type_cache.disassemble(field_type)))

return fields

Expand Down Expand Up @@ -433,8 +433,8 @@ class One(tp.Generic[T]):
type_cache = strcs.TypeCache()
typ1 = strcs.Type.create(One[B], cache=type_cache)
typ2 = strcs.Type.create(One[C], cache=type_cache)
typ1 = type_cache.disassemble(One[B])
typ2 = type_cache.disassemble(One[C])
assert typ1.mro.find_subtypes(A) == (B, )
assert typ2.mro.find_subtypes(A) == (C, )
Expand All @@ -455,11 +455,11 @@ class One(tp.Generic[T]):
f"The type has less typevars ({len(self.typevars)}) than wanted ({len(want)})"
)

typ = Type.create(typevars[(owner, tv)], expect=object, cache=self.type_cache)
typ = self.type_cache.disassemble(typevars[(owner, tv)])

if not issubclass(
typ.checkable,
Type.create(wa, cache=self.type_cache).checkable,
self.type_cache.disassemble(wa).checkable,
):
raise ValueError(
f"The concrete type {typ} is not a subclass of what was asked for {wa}"
Expand Down
4 changes: 1 addition & 3 deletions strcs/hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,7 @@ class Other:
value = tp.ForwardRef(value, is_argument=False, is_class=True)

if name in allfields:
from .disassemble import Type

disassembled = Type.create(value, cache=type_cache, expect=object)
disassembled = type_cache.disassemble(value)

resolved = resolve_type(disassembled.extracted, base_globals, base_locals)
if value != resolved and value in type_cache:
Expand Down
Loading

0 comments on commit 817bdea

Please sign in to comment.