Skip to content

Commit 2646cfd

Browse files
committed
WIP
1 parent 1d4f722 commit 2646cfd

File tree

10 files changed

+152
-61
lines changed

10 files changed

+152
-61
lines changed

classes/_registry.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from types import MethodType
21
from typing import Callable, Dict, NoReturn, Optional
32

43
TypeRegistry = Dict[type, Callable]
@@ -11,7 +10,7 @@ def choose_registry( # noqa: WPS211
1110
typ: type,
1211
is_protocol: bool,
1312
delegate: Optional[type],
14-
concretes: TypeRegistry,
13+
delegates: TypeRegistry,
1514
instances: TypeRegistry,
1615
protocols: TypeRegistry,
1716
) -> TypeRegistry:
@@ -21,12 +20,12 @@ def choose_registry( # noqa: WPS211
2120
It depends on how ``instance`` method is used and also on the type itself.
2221
"""
2322
if is_protocol and delegate is not None:
24-
raise ValueError('Both `is_protocol` and `delegated` are passed')
23+
raise ValueError('Both `is_protocol` and `delegate` are passed')
2524

2625
if is_protocol:
2726
return protocols
2827
elif delegate is not None:
29-
return concretes
28+
return delegates
3029
return instances
3130

3231

classes/_typeclass.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ class _TypeClass( # noqa: WPS214
312312
'_associated_type',
313313

314314
# Registry:
315-
'_concretes',
315+
'_delegates',
316316
'_instances',
317317
'_protocols',
318318

@@ -361,7 +361,7 @@ def __init__(
361361
self._associated_type = associated_type
362362

363363
# Registries:
364-
self._concretes: TypeRegistry = {}
364+
self._delegates: TypeRegistry = {}
365365
self._instances: TypeRegistry = {}
366366
self._protocols: TypeRegistry = {}
367367

@@ -418,13 +418,13 @@ def __call__(
418418
And all typeclasses that match ``Callable[[int, int], int]`` signature
419419
will typecheck.
420420
"""
421-
# At first, we try all our concrete types,
422-
# we don't cache it, because we cannot.
421+
# At first, we try all our delegate types,
422+
# we don't cache it, because it is impossible.
423423
# We only have runtime type info: `type([1]) == type(['a'])`.
424424
# It might be slow!
425-
# Don't add concrete types unless
425+
# Don't add any delegate types unless
426426
# you are absolutely know what you are doing.
427-
impl = self._dispatch_concrete(instance)
427+
impl = self._dispatch_delegate(instance)
428428
if impl is not None:
429429
return impl(instance, *args, **kwargs)
430430

@@ -499,21 +499,21 @@ def supports(
499499
See also: https://www.python.org/dev/peps/pep-0647
500500
"""
501501
# Here we first check that instance is already in the cache
502-
# and only then we check concrete types.
502+
# and only then we check delegate types.
503503
# Why?
504504
# Because if some type is already in the cache,
505-
# it means that it is not concrete.
505+
# it means that it is not a delegate.
506506
# So, this is simply faster.
507507
instance_type = type(instance)
508508
if instance_type in self._dispatch_cache:
509509
return True
510510

511-
# We never cache concrete types.
512-
if self._dispatch_concrete(instance) is not None:
511+
# We never cache delegate types.
512+
if self._dispatch_delegate(instance) is not None:
513513
return True
514514

515515
# This only happens when we don't have a cache in place
516-
# and this is not a concrete generic:
516+
# and this is not a delegate type:
517517
impl = self._dispatch(instance, instance_type)
518518
if impl is None:
519519
return False
@@ -535,7 +535,8 @@ def instance(
535535
536536
Args:
537537
is_protocol - required when passing protocols.
538-
delegate - required when using concrete generics like ``List[str]``.
538+
delegate - required when using delegate types, for example,
539+
when working with concrete generics like ``List[str]``.
539540
540541
Returns:
541542
Decorator for instance handler.
@@ -570,7 +571,7 @@ def decorator(implementation):
570571
typ=typ,
571572
is_protocol=is_protocol,
572573
delegate=delegate,
573-
concretes=self._concretes,
574+
delegates=self._delegates,
574575
instances=self._instances,
575576
protocols=self._protocols,
576577
)
@@ -600,9 +601,9 @@ def _dispatch(self, instance, instance_type: type) -> Optional[Callable]:
600601

601602
return _find_impl(instance_type, self._instances)
602603

603-
def _dispatch_concrete(self, instance) -> Optional[Callable]:
604-
for concrete, callback in self._concretes.items():
605-
if isinstance(instance, concrete):
604+
def _dispatch_delegate(self, instance) -> Optional[Callable]:
605+
for delegate, callback in self._delegates.items():
606+
if isinstance(instance, delegate):
606607
return callback
607608
return None
608609

classes/contrib/mypy/features/typeclass.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from classes.contrib.mypy.typeops import (
1818
call_signatures,
19+
fallback,
1920
instance_args,
2021
mro,
2122
type_loader,
@@ -184,10 +185,10 @@ class InstanceDefReturnType(object):
184185
185186
"""
186187

187-
# @fallback.error_to_any({
188-
# # TODO: later we can use a custom exception type for this:
189-
# KeyError: 'Typeclass cannot be loaded, it must be a global declaration',
190-
# })
188+
@fallback.error_to_any({
189+
# TODO: later we can use a custom exception type for this:
190+
KeyError: 'Typeclass cannot be loaded, it must be a global declaration',
191+
})
191192
def __call__(self, ctx: MethodContext) -> MypyType:
192193
"""Main entry point."""
193194
assert isinstance(ctx.type, Instance)

classes/contrib/mypy/typeops/instance_context.py

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818

1919
@final
2020
class InstanceContext(NamedTuple):
21-
""""""
21+
"""
22+
Instance definition context.
23+
24+
We use it to store all important types and data in one place
25+
to help with validation and type manipulations.
26+
"""
2227

2328
# Signatures:
2429
typeclass_signature: CallableType
@@ -41,8 +46,12 @@ class InstanceContext(NamedTuple):
4146
# Mypy context:
4247
ctx: MethodContext
4348

44-
@classmethod
45-
def build(
49+
@classmethod # noqa: WPS211
50+
def build( # noqa: WPS211
51+
# It has a lot of arguments, but I don't see how I can simply it.
52+
# I don't want to add steps or intermediate types.
53+
# It is okay for this method to have a lot arguments,
54+
# because it store a lot of data.
4655
cls,
4756
typeclass_signature: CallableType,
4857
instance_signature: CallableType,
@@ -51,6 +60,12 @@ def build(
5160
fullname: str,
5261
ctx: MethodContext,
5362
) -> 'InstanceContext':
63+
"""
64+
Builds instance context.
65+
66+
It also infers several missing parts from the present data.
67+
Like real ``instance_type`` and arg types.
68+
"""
5469
runtime_type = inference.infer_runtime_type_from_context(
5570
fallback=passed_args.items[0],
5671
fullname=fullname,
@@ -129,7 +144,41 @@ def _infer_instance_type(
129144
runtime_type: MypyType,
130145
delegate: Optional[MypyType],
131146
) -> MypyType:
132-
# TODO: document
147+
"""
148+
Infers real instance type.
149+
150+
We have three options here.
151+
First one, ``delegate`` is not set at all:
152+
153+
.. code:: python
154+
155+
@some.instance(list)
156+
def _some_list(instance: list) -> int:
157+
...
158+
159+
Then, infered instance type is just ``list``.
160+
161+
Second, we have a delegate of its own:
162+
163+
.. code:: python
164+
165+
@some.instance(list, delegate=SomeDelegate)
166+
def _some_list(instance: list) -> int:
167+
...
168+
169+
Then, infered instance type is ``list`` as well.
170+
171+
Lastly, we can have this case,
172+
when ``delegate`` type is used for instance annotation:
173+
174+
.. code:: python
175+
176+
@some.instance(list, delegate=SomeDelegate)
177+
def _some_list(instance: SomeDelegate) -> int:
178+
...
179+
180+
In this case, we will use runtime type ``list`` for instance type.
181+
"""
133182
if delegate is not None and is_same_type(instance_type, delegate):
134183
return runtime_type
135184
return instance_type

classes/contrib/mypy/typeops/mro.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22

33
from mypy.plugin import MethodContext
44
from mypy.subtypes import is_equivalent
5-
from mypy.types import Instance
5+
from mypy.types import Instance, ProperType
66
from mypy.types import Type as MypyType
77
from mypy.types import TypeVarType, UnionType, union_items
8-
from mypy.sametypes import is_same_type
98
from typing_extensions import final
109

1110
from classes.contrib.mypy.typeops import type_loader
@@ -68,7 +67,7 @@ class MetadataInjector(object):
6867

6968
def __init__(
7069
self,
71-
associated_type: Instance,
70+
associated_type: MypyType,
7271
instance_type: MypyType,
7372
delegate: Optional[MypyType],
7473
ctx: MethodContext,
@@ -80,12 +79,16 @@ def __init__(
8079
It supports ``Instance`` and ``Union`` types.
8180
"""
8281
self._associated_type = associated_type
83-
self._instance_types = union_items(instance_type)
8482
self._delegate = delegate
8583
self._ctx = ctx
8684

87-
if delegate is not None:
88-
self._instance_types.append(delegate)
85+
# If delegate is passed, we don't add any types to `instance`'s mro.
86+
# Why? See our `Delegate` docs.
87+
if delegate is None:
88+
self._instance_types = union_items(instance_type)
89+
else:
90+
assert isinstance(delegate, ProperType)
91+
self._instance_types = [delegate]
8992

9093
# Why do we store added types in a mutable global state?
9194
# Because, these types are hard to replicate without the proper context.
@@ -98,7 +101,8 @@ def add_supports_metadata(self) -> None:
98101
return
99102

100103
for instance_type in self._instance_types:
101-
assert isinstance(instance_type, Instance)
104+
if not isinstance(instance_type, Instance):
105+
continue
102106

103107
supports_type = _load_supports_type(
104108
associated_type=self._associated_type,
@@ -117,7 +121,6 @@ def add_supports_metadata(self) -> None:
117121
# This is the first time this type is referenced in
118122
# a typeclass'es instance defintinion.
119123
# Just inject `Supports` with no extra steps:
120-
print(instance_type, supports_type)
121124
instance_type.type.bases.append(supports_type)
122125

123126
if supports_type.type not in instance_type.type.mro:
@@ -132,8 +135,8 @@ def remove_supports_metadata(self) -> None:
132135
return
133136

134137
for instance_type in self._instance_types:
135-
assert isinstance(instance_type, Instance)
136-
self._clean_instance_type(instance_type)
138+
if isinstance(instance_type, Instance):
139+
self._clean_instance_type(instance_type)
137140
self._added_types = []
138141

139142
def _clean_instance_type(self, instance_type: Instance) -> None:
@@ -204,6 +207,10 @@ def _load_supports_type(
204207
delegate: Optional[MypyType],
205208
ctx: MethodContext,
206209
) -> Instance:
210+
# Why do have to modify args of `associated_type`?
211+
# Because `mypy` requires `type_var.id` to match,
212+
# otherwise, they would be treated as different variables.
213+
# That's why we copy the typevar definition from instance itself.
207214
supports_spec = associated_type.copy_modified(args=[
208215
TypeVarType(var_def)
209216
for var_def in instance_type.type.defn.type_vars

classes/contrib/mypy/validation/validate_instance/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88

99
def check_type(instance_context: InstanceContext) -> bool:
10+
"""Checks that instance definition is correct."""
1011
return all([
1112
validate_signature.check_type(instance_context),
1213
validate_runtime.check_type(instance_context),

classes/contrib/mypy/validation/validate_instance/validate_runtime.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545

4646

4747
def check_type(
48-
instance_context: InstanceContext
48+
instance_context: InstanceContext,
4949
) -> bool:
5050
"""
5151
Checks runtime type.
@@ -122,10 +122,10 @@ def _check_runtime_protocol(
122122
runtime_type: MypyType,
123123
ctx: MethodContext,
124124
*,
125-
is_protocol: bool,
125+
is_protocol: Optional[bool],
126126
) -> bool:
127127
if isinstance(runtime_type, Instance) and runtime_type.type:
128-
if not is_protocol and runtime_type.type.is_protocol:
128+
if is_protocol is False and runtime_type.type.is_protocol:
129129
ctx.api.fail(_IS_PROTOCOL_MISSING_MSG, ctx.context)
130130
return False
131131
elif is_protocol and not runtime_type.type.is_protocol:
@@ -193,7 +193,7 @@ def _check_tuple_size(
193193
delegate: Optional[MypyType],
194194
ctx: MethodContext,
195195
) -> bool:
196-
if delegate is None and isinstance(instance_type, TupleType):
196+
if isinstance(instance_type, TupleType):
197197
ctx.api.fail(
198198
_TUPLE_LENGTH_MSG.format(instance_type.items[0], instance_type),
199199
ctx.context,

docs/conf.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,6 @@
1919

2020
sys.path.insert(0, os.path.abspath('..'))
2121

22-
# TODO: Removes the whole if statement when the below PR is merged:
23-
# https://github.com/mgaitan/sphinxcontrib-mermaid/pull/71
24-
# From `sphinx>=4` the `ENOENT` constant was fully deprecated,
25-
# in order to make the things work with `sphinxcontrib-mermaid`
26-
# we need to mokey patch that constant.
27-
if sphinx.version_info[0] >= 4:
28-
import errno # noqa: WPS433
29-
30-
import sphinx.util.osutil # noqa: I003, WPS301, WPS433
31-
sphinx.util.osutil.ENOENT = errno.ENOENT
32-
3322

3423
# -- Project information -----------------------------------------------------
3524

poetry.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)