ovld 0.4.1__tar.gz → 0.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ovld-0.4.3/.bsync-snap-20240916114026.355340 +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/PKG-INFO +1 -1
- {ovld-0.4.1 → ovld-0.4.3}/pyproject.toml +1 -1
- ovld-0.4.3/reddit.md +54 -0
- ovld-0.4.3/reddit.py +24 -0
- {ovld-0.4.1 → ovld-0.4.3}/src/ovld/__init__.py +8 -0
- {ovld-0.4.1 → ovld-0.4.3}/src/ovld/core.py +12 -3
- {ovld-0.4.1 → ovld-0.4.3}/src/ovld/dependent.py +16 -9
- {ovld-0.4.1 → ovld-0.4.3}/src/ovld/mro.py +16 -15
- {ovld-0.4.1 → ovld-0.4.3}/src/ovld/recode.py +7 -5
- {ovld-0.4.1 → ovld-0.4.3}/src/ovld/typemap.py +48 -21
- {ovld-0.4.1 → ovld-0.4.3}/src/ovld/types.py +26 -14
- ovld-0.4.3/src/ovld/version.py +1 -0
- ovld-0.4.3/tests/test_mro.py +112 -0
- {ovld-0.4.1 → ovld-0.4.3}/tests/test_ovld.py +59 -0
- {ovld-0.4.1 → ovld-0.4.3}/tests/test_typemap.py +1 -1
- {ovld-0.4.1 → ovld-0.4.3}/todo.q +4 -0
- {ovld-0.4.1 → ovld-0.4.3}/uv.lock +1 -1
- ovld-0.4.1/src/ovld/version.py +0 -1
- ovld-0.4.1/tests/test_mro.py +0 -51
- {ovld-0.4.1 → ovld-0.4.3}/.bsync-snap-20220324145902.852076 +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/.envrc +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/.github/workflows/python-package.yml +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/.gitignore +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/.python-version +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/.readthedocs.yaml +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/LICENSE +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/README.md +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/add.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/anal.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/bench/requirements.txt +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/bench.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/benchd.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/benchmarks/__init__.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/benchmarks/common.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/benchmarks/test_add.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/benchmarks/test_ast.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/benchmarks/test_calc.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/benchmarks/test_fib.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/benchmarks/test_multer.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/benchmarks/test_trivial.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/benchmarks/test_tweaknum.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/cloz.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/data.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/didi.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/docs/compare.md +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/docs/dependent.md +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/docs/features.md +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/docs/index.md +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/docs/types.md +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/docs/usage.md +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/doo.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/edges.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/explore.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/facto.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/gen_comparison_table.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/gentest.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/hello.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/hntest.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/klos.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/mkdocs.yml +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/nxt.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.0-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.0.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.1-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.1.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.2-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.2.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.3-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.3.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.4-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.4.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.5-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.5.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.6-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.6.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.7-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.7.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.8-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.8.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.9-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.9.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.0-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.0.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.1-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.1.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.10-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.10.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.11-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.11.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.2-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.2.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.3-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.3.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.4-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.4.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.5-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.5.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.6-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.6.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.7-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.7.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.8-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.8.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.9-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.9.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.0-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.0.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.1-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.1.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.2-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.2.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.3-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.3.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.4-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.4.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.5-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.5.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.8-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.8.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.9-py3-none-any.whl +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.9.tar.gz +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/one_two_three.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/outoftheway.toml +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/src/ovld/utils.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/stuff.md +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/tb.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/tensor.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/tests/__init__.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/tests/modules/gingerbread.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/tests/test_dependent.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/tests/test_examples.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/tests/test_global.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/tests/test_ovld/test_display.txt +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/tests/test_ovld/test_doc.txt +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/tests/test_ovld/test_doc2.txt +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/tests/test_ovld/test_method_doc.txt +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/tests/test_types.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/tests/test_utils.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/toot.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/typo.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/world.yaml +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/x.py +0 -0
- {ovld-0.4.1 → ovld-0.4.3}/zaggo +0 -0
Binary file
|
ovld-0.4.3/reddit.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
## What My Project Does
|
2
|
+
|
3
|
+
[ovld](https://github.com/breuleux/ovld) implements multiple dispatch in Python. This lets you define multiple versions of the same function with different type signatures.
|
4
|
+
|
5
|
+
For example:
|
6
|
+
|
7
|
+
```python
|
8
|
+
import math
|
9
|
+
from typing import Literal
|
10
|
+
from ovld import ovld
|
11
|
+
|
12
|
+
@ovld
|
13
|
+
def div(x: int, y: int):
|
14
|
+
return x / y
|
15
|
+
|
16
|
+
@ovld
|
17
|
+
def div(x: str, y: str):
|
18
|
+
return f"{x}/{y}"
|
19
|
+
|
20
|
+
@ovld
|
21
|
+
def div(x: int, y: Literal[0]):
|
22
|
+
return math.inf
|
23
|
+
|
24
|
+
assert div(8, 2) == 4
|
25
|
+
assert div("/home", "user") == "/home/user"
|
26
|
+
assert div(10, 0) == math.inf
|
27
|
+
```
|
28
|
+
|
29
|
+
|
30
|
+
## Target Audience
|
31
|
+
|
32
|
+
Ovld is pretty generally applicable: multiple dispatch is a central feature of several programming languages, e.g. Julia. I find it particularly useful when doing work on complex heterogeneous data structures, for instance walking an AST, serializing/deserializing data, generating HTML representations of data, etc.
|
33
|
+
|
34
|
+
|
35
|
+
## Features
|
36
|
+
|
37
|
+
* Wide range of supported annotations: normal types, protocols, Union, Literal, [custom types](https://ovld.readthedocs.io/en/latest/types/#defining-new-types) such as HasMethod, Intersection and arbitrary user-defined types.
|
38
|
+
* Support for [dependent types](https://ovld.readthedocs.io/en/latest/dependent/), by which I mean "types" that depend on the values of the arguments. For example you can easily implement a `Regexp[regex]` type that matches string arguments based on regular expressions, or a type that only matches 2x2 torch.Tensor with int8 dtype.
|
39
|
+
* Dispatch on keyword arguments (with a few limitations).
|
40
|
+
* Define numeric priority levels for disambiguation.
|
41
|
+
* Define [variants](https://ovld.readthedocs.io/en/latest/usage/#variants) of existing functions (copies of existing overloads with additional functionality)
|
42
|
+
* Special `recurse()` function for recursive calls that also work with variants.
|
43
|
+
* Special `call_next()` function to call the next dispatch.
|
44
|
+
|
45
|
+
|
46
|
+
## Comparison
|
47
|
+
|
48
|
+
There already exist a few multiple dispatch libraries: plum, multimethod, multipledispatch, runtype, fastcore, and the builtin functools.singledispatch (single argument).
|
49
|
+
|
50
|
+
Ovld is faster than all of them. From 1.5x to 100x faster depending on use case, and in the ballpark of isinstance/match. It is also generally more featureful: no other library supports dispatch on keyword arguments, and only a few support `Literal` annotations, but with massive performance penalties.
|
51
|
+
|
52
|
+
You can also do many things ovld does with the `match` statement, except you cannot extend existing `match` statements with more signatures.
|
53
|
+
|
54
|
+
[Whole comparison section, with benchmarks, can be found here.](https://ovld.readthedocs.io/en/latest/compare/)
|
ovld-0.4.3/reddit.py
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
import math
|
2
|
+
from typing import Literal
|
3
|
+
|
4
|
+
from ovld import ovld
|
5
|
+
|
6
|
+
|
7
|
+
@ovld
|
8
|
+
def div(x: int, y: int):
|
9
|
+
return x / y
|
10
|
+
|
11
|
+
|
12
|
+
@ovld
|
13
|
+
def div(x: str, y: str):
|
14
|
+
return f"{x}/{y}"
|
15
|
+
|
16
|
+
|
17
|
+
@ovld
|
18
|
+
def div(x: int, y: Literal[0]):
|
19
|
+
return math.inf
|
20
|
+
|
21
|
+
|
22
|
+
assert div(8, 2) == 4
|
23
|
+
assert div("/home", "user") == "/home/user"
|
24
|
+
assert div(10, 0) == math.inf
|
@@ -15,6 +15,11 @@ from .dependent import (
|
|
15
15
|
ParametrizedDependentType,
|
16
16
|
dependent_check,
|
17
17
|
)
|
18
|
+
from .mro import (
|
19
|
+
TypeRelationship,
|
20
|
+
subclasscheck,
|
21
|
+
typeorder,
|
22
|
+
)
|
18
23
|
from .recode import call_next, recurse
|
19
24
|
from .typemap import (
|
20
25
|
MultiTypeMap,
|
@@ -66,6 +71,9 @@ __all__ = [
|
|
66
71
|
"Intersection",
|
67
72
|
"StrictSubclass",
|
68
73
|
"class_check",
|
74
|
+
"subclasscheck",
|
75
|
+
"typeorder",
|
76
|
+
"TypeRelationship",
|
69
77
|
"parametrized_class_check",
|
70
78
|
"keyword_decorator",
|
71
79
|
"call_next",
|
@@ -85,6 +85,7 @@ class Signature:
|
|
85
85
|
req_names: frozenset
|
86
86
|
vararg: bool
|
87
87
|
priority: float
|
88
|
+
tiebreak: int = 0
|
88
89
|
is_method: bool = False
|
89
90
|
arginfo: list[Arginfo] = field(
|
90
91
|
default_factory=list, hash=False, compare=False
|
@@ -393,8 +394,8 @@ class _Ovld:
|
|
393
394
|
)
|
394
395
|
else:
|
395
396
|
hlp = ""
|
396
|
-
for
|
397
|
-
hlp += f"* {
|
397
|
+
for c in possibilities:
|
398
|
+
hlp += f"* {c.handler.__name__} (priority: {c.priority}, specificity: {list(c.specificity)})\n"
|
398
399
|
return TypeError(
|
399
400
|
f"Ambiguous resolution in {self} for"
|
400
401
|
f" argument types [{typenames}]\n"
|
@@ -503,7 +504,15 @@ class _Ovld:
|
|
503
504
|
raise TypeError(
|
504
505
|
f"There is already a method for {sigstring(sig.types)}"
|
505
506
|
)
|
506
|
-
|
507
|
+
|
508
|
+
def _set(sig, fn):
|
509
|
+
if sig in self._defns:
|
510
|
+
# Push down the existing handler with a lower tiebreak
|
511
|
+
msig = replace(sig, tiebreak=sig.tiebreak - 1)
|
512
|
+
_set(msig, self._defns[sig])
|
513
|
+
self._defns[sig] = fn
|
514
|
+
|
515
|
+
_set(sig, fn)
|
507
516
|
|
508
517
|
self._update()
|
509
518
|
return self
|
@@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Any, Mapping, TypeVar, Union
|
|
7
7
|
from .types import (
|
8
8
|
Intersection,
|
9
9
|
Order,
|
10
|
-
TypeRelationship,
|
11
10
|
normalize_type,
|
12
11
|
subclasscheck,
|
13
12
|
typeorder,
|
@@ -67,26 +66,34 @@ class DependentType:
|
|
67
66
|
def codegen(self):
|
68
67
|
return CodeGen("{this}.check({arg})", {"this": self})
|
69
68
|
|
70
|
-
def
|
69
|
+
def __type_order__(self, other):
|
71
70
|
if isinstance(other, DependentType):
|
72
71
|
order = typeorder(self.bound, other.bound)
|
73
72
|
if order is Order.SAME:
|
74
73
|
# It isn't fully deterministic which of these will be called
|
75
74
|
# because of set ordering between the types we compare
|
76
75
|
if self < other: # pragma: no cover
|
77
|
-
return
|
76
|
+
return Order.LESS
|
78
77
|
elif other < self: # pragma: no cover
|
79
|
-
return
|
78
|
+
return Order.MORE
|
80
79
|
else:
|
81
|
-
return
|
80
|
+
return Order.NONE
|
82
81
|
else: # pragma: no cover
|
83
|
-
return
|
84
|
-
elif
|
82
|
+
return order
|
83
|
+
elif subclasscheck(other, self.bound) or subclasscheck(
|
85
84
|
self.bound, other
|
86
85
|
):
|
87
|
-
return
|
86
|
+
return Order.LESS
|
88
87
|
else:
|
89
|
-
return
|
88
|
+
return Order.NONE
|
89
|
+
|
90
|
+
def __is_supertype__(self, other):
|
91
|
+
if isinstance(other, DependentType):
|
92
|
+
return False
|
93
|
+
elif subclasscheck(other, self.bound):
|
94
|
+
return True
|
95
|
+
else:
|
96
|
+
return False
|
90
97
|
|
91
98
|
def __lt__(self, other):
|
92
99
|
return False
|
@@ -22,7 +22,8 @@ class Order(Enum):
|
|
22
22
|
@dataclass
|
23
23
|
class TypeRelationship:
|
24
24
|
order: Order
|
25
|
-
|
25
|
+
supertype: bool = NotImplemented
|
26
|
+
subtype: bool = NotImplemented
|
26
27
|
|
27
28
|
|
28
29
|
def _issubclass(t1, t2):
|
@@ -48,19 +49,16 @@ def typeorder(t1, t2):
|
|
48
49
|
if t1 == t2:
|
49
50
|
return Order.SAME
|
50
51
|
|
51
|
-
t1 = getattr(t1, "__proxy_for__", t1)
|
52
|
-
t2 = getattr(t2, "__proxy_for__", t2)
|
53
|
-
|
54
52
|
if (
|
55
|
-
hasattr(t1, "
|
56
|
-
and (result := t1.
|
53
|
+
hasattr(t1, "__type_order__")
|
54
|
+
and (result := t1.__type_order__(t2)) is not NotImplemented
|
57
55
|
):
|
58
|
-
return result
|
56
|
+
return result
|
59
57
|
elif (
|
60
|
-
hasattr(t2, "
|
61
|
-
and (result := t2.
|
58
|
+
hasattr(t2, "__type_order__")
|
59
|
+
and (result := t2.__type_order__(t1)) is not NotImplemented
|
62
60
|
):
|
63
|
-
return result.
|
61
|
+
return result.opposite()
|
64
62
|
|
65
63
|
o1 = getattr(t1, "__origin__", None)
|
66
64
|
o2 = getattr(t2, "__origin__", None)
|
@@ -135,14 +133,17 @@ def subclasscheck(t1, t2):
|
|
135
133
|
if t1 == t2:
|
136
134
|
return True
|
137
135
|
|
138
|
-
|
139
|
-
|
136
|
+
if (
|
137
|
+
hasattr(t2, "__is_supertype__")
|
138
|
+
and (result := t2.__is_supertype__(t1)) is not NotImplemented
|
139
|
+
):
|
140
|
+
return result
|
140
141
|
|
141
142
|
if (
|
142
|
-
hasattr(
|
143
|
-
and (result :=
|
143
|
+
hasattr(t1, "__is_subtype__")
|
144
|
+
and (result := t1.__is_subtype__(t2)) is not NotImplemented
|
144
145
|
):
|
145
|
-
return result
|
146
|
+
return result
|
146
147
|
|
147
148
|
o1 = getattr(t1, "__origin__", None)
|
148
149
|
o2 = getattr(t2, "__origin__", None)
|
@@ -373,6 +373,7 @@ class NameConverter(ast.NodeTransformer):
|
|
373
373
|
self.call_next_sym = call_next_sym
|
374
374
|
self.ovld_mangled = ovld_mangled
|
375
375
|
self.code_mangled = code_mangled
|
376
|
+
self.count = count()
|
376
377
|
|
377
378
|
def visit_Name(self, node):
|
378
379
|
if node.id == self.recurse_sym:
|
@@ -396,15 +397,16 @@ class NameConverter(ast.NodeTransformer):
|
|
396
397
|
return self.generic_visit(node)
|
397
398
|
|
398
399
|
cn = node.func.id == self.call_next_sym
|
400
|
+
tmp = f"__TMP{next(self.count)}_"
|
399
401
|
|
400
402
|
def _make_lookup_call(key, arg):
|
401
403
|
value = ast.NamedExpr(
|
402
|
-
target=ast.Name(id=f"
|
404
|
+
target=ast.Name(id=f"{tmp}{key}", ctx=ast.Store()),
|
403
405
|
value=self.visit(arg),
|
404
406
|
)
|
405
407
|
if self.analysis.lookup_for(key) == "self.map.transform":
|
406
408
|
func = ast.Attribute(
|
407
|
-
value=ast.Name(id="
|
409
|
+
value=ast.Name(id=f"{tmp}M", ctx=ast.Load()),
|
408
410
|
attr="transform",
|
409
411
|
ctx=ast.Load(),
|
410
412
|
)
|
@@ -437,7 +439,7 @@ class NameConverter(ast.NodeTransformer):
|
|
437
439
|
type_parts.insert(0, ast.Name(id=self.code_mangled, ctx=ast.Load()))
|
438
440
|
method = ast.Subscript(
|
439
441
|
value=ast.NamedExpr(
|
440
|
-
target=ast.Name(id="
|
442
|
+
target=ast.Name(id=f"{tmp}M", ctx=ast.Store()),
|
441
443
|
value=ast.Attribute(
|
442
444
|
value=ast.Name(id=self.ovld_mangled, ctx=ast.Load()),
|
443
445
|
attr="map",
|
@@ -464,13 +466,13 @@ class NameConverter(ast.NodeTransformer):
|
|
464
466
|
new_node = ast.Call(
|
465
467
|
func=method,
|
466
468
|
args=[
|
467
|
-
ast.Name(id=f"
|
469
|
+
ast.Name(id=f"{tmp}{i}", ctx=ast.Load())
|
468
470
|
for i, arg in enumerate(node.args)
|
469
471
|
],
|
470
472
|
keywords=[
|
471
473
|
ast.keyword(
|
472
474
|
arg=kw.arg,
|
473
|
-
value=ast.Name(id=f"
|
475
|
+
value=ast.Name(id=f"{tmp}{kw.arg}", ctx=ast.Load()),
|
474
476
|
)
|
475
477
|
for kw in node.keywords
|
476
478
|
],
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import inspect
|
2
2
|
import math
|
3
3
|
import typing
|
4
|
+
from dataclasses import dataclass
|
4
5
|
from itertools import count
|
5
6
|
from types import CodeType
|
6
7
|
|
@@ -69,6 +70,27 @@ class TypeMap(dict):
|
|
69
70
|
raise KeyError(obj_t)
|
70
71
|
|
71
72
|
|
73
|
+
@dataclass
|
74
|
+
class Candidate:
|
75
|
+
handler: object
|
76
|
+
priority: float
|
77
|
+
specificity: tuple
|
78
|
+
tiebreak: int
|
79
|
+
|
80
|
+
def sort_key(self):
|
81
|
+
return self.priority, sum(self.specificity), self.tiebreak
|
82
|
+
|
83
|
+
def dominates(self, other):
|
84
|
+
if self.priority > other.priority:
|
85
|
+
return True
|
86
|
+
elif self.specificity != other.specificity:
|
87
|
+
return all(
|
88
|
+
s1 >= s2 for s1, s2 in zip(self.specificity, other.specificity)
|
89
|
+
)
|
90
|
+
else:
|
91
|
+
return self.tiebreak > other.tiebreak
|
92
|
+
|
93
|
+
|
72
94
|
class MultiTypeMap(dict):
|
73
95
|
"""Represents a mapping from tuples of types to handlers.
|
74
96
|
|
@@ -91,6 +113,7 @@ class MultiTypeMap(dict):
|
|
91
113
|
def __init__(self, name="_ovld", key_error=KeyError):
|
92
114
|
self.maps = {}
|
93
115
|
self.priorities = {}
|
116
|
+
self.tiebreaks = {}
|
94
117
|
self.dependent = {}
|
95
118
|
self.type_tuples = {}
|
96
119
|
self.empty = MISSING
|
@@ -155,7 +178,12 @@ class MultiTypeMap(dict):
|
|
155
178
|
specificities.setdefault(c, []).append(results[c])
|
156
179
|
|
157
180
|
candidates = [
|
158
|
-
(
|
181
|
+
Candidate(
|
182
|
+
handler=c,
|
183
|
+
priority=self.priorities.get(c, 0),
|
184
|
+
specificity=tuple(specificities[c]),
|
185
|
+
tiebreak=self.tiebreaks.get(c, 0),
|
186
|
+
)
|
159
187
|
for c in candidates
|
160
188
|
]
|
161
189
|
|
@@ -164,33 +192,30 @@ class MultiTypeMap(dict):
|
|
164
192
|
# other possibilities on all arguments, so the sum of all specificities
|
165
193
|
# has to be greater.
|
166
194
|
# Note: priority is always more important than specificity
|
167
|
-
|
195
|
+
|
196
|
+
candidates.sort(key=Candidate.sort_key, reverse=True)
|
168
197
|
|
169
198
|
self.all[obj_t_tup] = {
|
170
|
-
getattr(c
|
199
|
+
getattr(c.handler, "__code__", None) for c in candidates
|
171
200
|
}
|
172
201
|
|
173
202
|
processed = set()
|
174
203
|
|
175
204
|
def _pull(candidates):
|
176
|
-
candidates = [
|
177
|
-
(c, a, b) for (c, a, b) in candidates if c not in processed
|
178
|
-
]
|
205
|
+
candidates = [c for c in candidates if c.handler not in processed]
|
179
206
|
if not candidates:
|
180
207
|
return
|
181
208
|
rval = [candidates[0]]
|
182
|
-
c1
|
183
|
-
for c2
|
184
|
-
if
|
185
|
-
spc1 != spc2 and all(s1 >= s2 for s1, s2 in zip(spc1, spc2))
|
186
|
-
):
|
209
|
+
c1 = candidates[0]
|
210
|
+
for c2 in candidates[1:]:
|
211
|
+
if c1.dominates(c2):
|
187
212
|
# Candidate 1 dominates candidate 2
|
188
213
|
continue
|
189
214
|
else:
|
190
|
-
processed.add(c2)
|
215
|
+
processed.add(c2.handler)
|
191
216
|
# Candidate 1 does not dominate candidate 2, so we add it
|
192
217
|
# to the list.
|
193
|
-
rval.append(
|
218
|
+
rval.append(c2)
|
194
219
|
yield rval
|
195
220
|
if len(rval) >= 1:
|
196
221
|
yield from _pull(candidates[1:])
|
@@ -212,6 +237,7 @@ class MultiTypeMap(dict):
|
|
212
237
|
self.empty = entry
|
213
238
|
|
214
239
|
self.priorities[handler] = sig.priority
|
240
|
+
self.tiebreaks[handler] = sig.tiebreak
|
215
241
|
self.type_tuples[handler] = obj_t_tup
|
216
242
|
self.dependent[handler] = any(
|
217
243
|
isinstance(t[1] if isinstance(t, tuple) else t, DependentType)
|
@@ -257,15 +283,16 @@ class MultiTypeMap(dict):
|
|
257
283
|
finished = False
|
258
284
|
rank = 1
|
259
285
|
for grp in self.mro(tuple(argt)):
|
260
|
-
grp.sort(key=lambda x: x
|
286
|
+
grp.sort(key=lambda x: x.handler.__name__)
|
261
287
|
match = [
|
262
288
|
dependent_match(
|
263
|
-
self.type_tuples[handler], [*args, *kwargs.items()]
|
289
|
+
self.type_tuples[c.handler], [*args, *kwargs.items()]
|
264
290
|
)
|
265
|
-
for
|
291
|
+
for c in grp
|
266
292
|
]
|
267
293
|
ambiguous = len([m for m in match if m]) > 1
|
268
|
-
for m,
|
294
|
+
for m, c in zip(match, grp):
|
295
|
+
handler = c.handler
|
269
296
|
color = "\033[0m"
|
270
297
|
if finished:
|
271
298
|
bullet = "--"
|
@@ -282,8 +309,8 @@ class MultiTypeMap(dict):
|
|
282
309
|
message = f"{handler.__name__} will be called first."
|
283
310
|
color = "\033[1;32m"
|
284
311
|
rank += 1
|
285
|
-
spec = ".".join(map(str,
|
286
|
-
lvl = f"[{
|
312
|
+
spec = ".".join(map(str, c.specificity))
|
313
|
+
lvl = f"[{c.priority}:{spec}]"
|
287
314
|
width = 2 * len(args) + 6
|
288
315
|
print(f"{color}{bullet} {lvl:{width}} {handler.__name__}")
|
289
316
|
co = handler.__code__
|
@@ -320,8 +347,8 @@ class MultiTypeMap(dict):
|
|
320
347
|
|
321
348
|
funcs = []
|
322
349
|
for group in reversed(results):
|
323
|
-
handlers = [
|
324
|
-
dependent = any(self.dependent[
|
350
|
+
handlers = [c.handler for c in group]
|
351
|
+
dependent = any(self.dependent[c.handler] for c in group)
|
325
352
|
if dependent:
|
326
353
|
nxt = self.wrap_dependent(
|
327
354
|
obj_t_tup, handlers, group, funcs[-1] if funcs else None
|
@@ -61,19 +61,31 @@ class MetaMC(type):
|
|
61
61
|
def __init__(cls, name, order):
|
62
62
|
pass
|
63
63
|
|
64
|
-
def
|
65
|
-
|
66
|
-
if isinstance(
|
67
|
-
return
|
64
|
+
def __type_order__(cls, other):
|
65
|
+
results = cls.order(other)
|
66
|
+
if isinstance(results, TypeRelationship):
|
67
|
+
return results.order
|
68
68
|
else:
|
69
|
-
return
|
69
|
+
return NotImplemented
|
70
|
+
|
71
|
+
def __is_supertype__(cls, other):
|
72
|
+
results = cls.order(other)
|
73
|
+
if isinstance(results, bool):
|
74
|
+
return results
|
75
|
+
elif isinstance(results, TypeRelationship):
|
76
|
+
return results.supertype
|
77
|
+
else: # pragma: no cover
|
78
|
+
return NotImplemented
|
79
|
+
|
80
|
+
def __is_subtype__(cls, other):
|
81
|
+
results = cls.order(other)
|
82
|
+
if isinstance(results, TypeRelationship):
|
83
|
+
return results.subtype
|
84
|
+
else: # pragma: no cover
|
85
|
+
return NotImplemented
|
70
86
|
|
71
87
|
def __subclasscheck__(cls, sub):
|
72
|
-
|
73
|
-
if isinstance(result, TypeRelationship):
|
74
|
-
return result.order in (Order.MORE, Order.SAME)
|
75
|
-
else:
|
76
|
-
return result
|
88
|
+
return cls.__is_supertype__(sub)
|
77
89
|
|
78
90
|
|
79
91
|
def class_check(condition):
|
@@ -164,7 +176,7 @@ def Exactly(cls, base_cls):
|
|
164
176
|
"""Match the class but not its subclasses."""
|
165
177
|
return TypeRelationship(
|
166
178
|
order=Order.LESS if cls is base_cls else typeorder(base_cls, cls),
|
167
|
-
|
179
|
+
supertype=cls is base_cls,
|
168
180
|
)
|
169
181
|
|
170
182
|
|
@@ -184,11 +196,11 @@ def Intersection(cls, *classes):
|
|
184
196
|
matches = all(subclasscheck(cls, t) for t in classes)
|
185
197
|
compare = [x for t in classes if (x := typeorder(t, cls)) is not Order.NONE]
|
186
198
|
if not compare:
|
187
|
-
return TypeRelationship(Order.NONE,
|
199
|
+
return TypeRelationship(Order.NONE, supertype=matches)
|
188
200
|
elif any(x is Order.LESS or x is Order.SAME for x in compare):
|
189
|
-
return TypeRelationship(Order.LESS,
|
201
|
+
return TypeRelationship(Order.LESS, supertype=matches)
|
190
202
|
else:
|
191
|
-
return TypeRelationship(Order.MORE,
|
203
|
+
return TypeRelationship(Order.MORE, supertype=matches)
|
192
204
|
|
193
205
|
|
194
206
|
@parametrized_class_check
|
@@ -0,0 +1 @@
|
|
1
|
+
version = "0.4.3"
|
@@ -0,0 +1,112 @@
|
|
1
|
+
try:
|
2
|
+
from types import UnionType
|
3
|
+
except ImportError: # pragma: no cover
|
4
|
+
UnionType = None
|
5
|
+
|
6
|
+
import sys
|
7
|
+
from typing import Union
|
8
|
+
|
9
|
+
from ovld.dependent import Dependent
|
10
|
+
from ovld.mro import Order, subclasscheck, typeorder
|
11
|
+
|
12
|
+
|
13
|
+
def identity(x):
|
14
|
+
return x
|
15
|
+
|
16
|
+
|
17
|
+
class A:
|
18
|
+
pass
|
19
|
+
|
20
|
+
|
21
|
+
class B(A):
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
class Prox:
|
26
|
+
_cls = object
|
27
|
+
|
28
|
+
def __class_getitem__(cls, other):
|
29
|
+
return type(f"Prox[{other.__name__}]", (Prox,), {"_cls": other})
|
30
|
+
|
31
|
+
@classmethod
|
32
|
+
def __is_supertype__(cls, other):
|
33
|
+
return subclasscheck(other, cls._cls)
|
34
|
+
|
35
|
+
@classmethod
|
36
|
+
def __is_subtype__(cls, other):
|
37
|
+
return subclasscheck(cls._cls, other)
|
38
|
+
|
39
|
+
@classmethod
|
40
|
+
def __type_order__(cls, other):
|
41
|
+
return typeorder(cls._cls, other)
|
42
|
+
|
43
|
+
|
44
|
+
def test_subclasscheck():
|
45
|
+
assert subclasscheck(B, A)
|
46
|
+
assert not subclasscheck(A, B)
|
47
|
+
assert subclasscheck(A, A)
|
48
|
+
assert subclasscheck(A, object)
|
49
|
+
assert subclasscheck(A, Union[A, int])
|
50
|
+
assert subclasscheck(int, Union[A, int])
|
51
|
+
|
52
|
+
|
53
|
+
def test_subclasscheck_generic():
|
54
|
+
assert subclasscheck(list[int], list[int])
|
55
|
+
assert subclasscheck(list[int], list[object])
|
56
|
+
assert not subclasscheck(list[object], list[int])
|
57
|
+
|
58
|
+
|
59
|
+
def test_subclasscheck_type():
|
60
|
+
assert subclasscheck(type[int], type[object])
|
61
|
+
|
62
|
+
|
63
|
+
def test_subclasscheck_type_union():
|
64
|
+
if sys.version_info >= (3, 10):
|
65
|
+
assert subclasscheck(type[int | str], type[UnionType])
|
66
|
+
assert subclasscheck(type[Union[int, str]], type[Union])
|
67
|
+
assert subclasscheck(type[Union], object)
|
68
|
+
assert not subclasscheck(object, type[Union])
|
69
|
+
|
70
|
+
|
71
|
+
def test_typeorder_type_union():
|
72
|
+
if sys.version_info >= (3, 10):
|
73
|
+
assert typeorder(type[int | str], type[UnionType]) is Order.LESS
|
74
|
+
assert typeorder(type[Union[int, str]], type[Union]) is Order.LESS
|
75
|
+
assert typeorder(object, type[Union]) is Order.MORE
|
76
|
+
|
77
|
+
|
78
|
+
def test_subclasscheck_dependent():
|
79
|
+
assert subclasscheck(B, Dependent[A, identity])
|
80
|
+
assert not subclasscheck(
|
81
|
+
Dependent[int, identity], Dependent[float, identity]
|
82
|
+
)
|
83
|
+
|
84
|
+
|
85
|
+
def test_typeorder_dependent():
|
86
|
+
assert typeorder(Dependent[int, identity], float) is Order.NONE
|
87
|
+
assert (
|
88
|
+
typeorder(Dependent[int, identity], Dependent[A, identity])
|
89
|
+
is Order.NONE
|
90
|
+
)
|
91
|
+
assert (
|
92
|
+
typeorder(Dependent[int, identity], Dependent[float, identity])
|
93
|
+
is Order.NONE
|
94
|
+
)
|
95
|
+
|
96
|
+
|
97
|
+
def test_subclasscheck_proxy():
|
98
|
+
assert subclasscheck(Prox[int], int)
|
99
|
+
assert subclasscheck(int, Prox[int])
|
100
|
+
assert subclasscheck(Prox[int], Prox)
|
101
|
+
assert not subclasscheck(Prox, Prox[int])
|
102
|
+
|
103
|
+
assert subclasscheck(Prox[B], A)
|
104
|
+
assert not subclasscheck(Prox[A], B)
|
105
|
+
assert subclasscheck(B, Prox[A])
|
106
|
+
assert not subclasscheck(A, Prox[B])
|
107
|
+
assert subclasscheck(Prox[B], Prox[A])
|
108
|
+
|
109
|
+
assert typeorder(Prox[int], int) is Order.SAME
|
110
|
+
assert typeorder(int, Prox[int]) is Order.SAME
|
111
|
+
assert typeorder(Prox[int], Prox) is Order.LESS
|
112
|
+
assert typeorder(Prox, Prox[int]) is Order.MORE
|