ovld 0.5.5__tar.gz → 0.5.7__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.5.5 → ovld-0.5.7}/PKG-INFO +54 -13
- {ovld-0.5.5 → ovld-0.5.7}/README.md +53 -12
- {ovld-0.5.5 → ovld-0.5.7}/docs/index.md +47 -7
- {ovld-0.5.5 → ovld-0.5.7}/pyproject.toml +3 -1
- {ovld-0.5.5 → ovld-0.5.7}/src/ovld/core.py +6 -0
- {ovld-0.5.5 → ovld-0.5.7}/src/ovld/dependent.py +2 -9
- {ovld-0.5.5 → ovld-0.5.7}/src/ovld/mro.py +6 -1
- {ovld-0.5.5 → ovld-0.5.7}/src/ovld/recode.py +1 -3
- {ovld-0.5.5 → ovld-0.5.7}/src/ovld/typemap.py +25 -14
- {ovld-0.5.5 → ovld-0.5.7}/src/ovld/types.py +6 -7
- {ovld-0.5.5 → ovld-0.5.7}/src/ovld/utils.py +29 -0
- ovld-0.5.7/src/ovld/version.py +1 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_dependent.py +28 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_ovld.py +41 -1
- {ovld-0.5.5 → ovld-0.5.7}/uv.lock +333 -327
- ovld-0.5.5/src/ovld/version.py +0 -1
- {ovld-0.5.5 → ovld-0.5.7}/.github/workflows/publish.yml +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/.github/workflows/python-package.yml +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/.gitignore +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/.python-version +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/.readthedocs.yaml +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/LICENSE +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/benchmarks/__init__.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/benchmarks/common.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/benchmarks/conftest.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_add.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_ast.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_calc.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_fib.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_multer.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_regexp.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_trivial.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_tweaknum.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/docs/codegen.md +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/docs/compare.md +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/docs/dependent.md +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/docs/features.md +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/docs/medley.md +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/docs/types.md +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/docs/usage.md +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/mkdocs.yml +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/src/ovld/__init__.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/src/ovld/abc.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/src/ovld/codegen.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/src/ovld/medley.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/src/ovld/py.typed +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/src/ovld/signatures.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/__init__.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/modules/gingerbread.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_abc.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_codegen/test_dataclass_gen.txt +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_codegen/test_method.txt +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_codegen/test_method_metaclass.txt +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_codegen/test_method_per_instance.txt +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_codegen/test_simple.txt +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_codegen/test_variant_generation.txt +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_codegen.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_examples.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_global.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_medley.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_mro.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_ovld/test_display.txt +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_ovld/test_display_more.txt +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_ovld/test_doc.txt +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_ovld/test_doc2.txt +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_ovld/test_method_doc.txt +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_typemap.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_types.py +0 -0
- {ovld-0.5.5 → ovld-0.5.7}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ovld
|
3
|
-
Version: 0.5.
|
3
|
+
Version: 0.5.7
|
4
4
|
Summary: Overloading Python functions
|
5
5
|
Project-URL: Homepage, https://ovld.readthedocs.io/en/latest/
|
6
6
|
Project-URL: Documentation, https://ovld.readthedocs.io/en/latest/
|
@@ -22,13 +22,53 @@ With ovld, you can write a version of the same function for every type signature
|
|
22
22
|
|
23
23
|
* ⚡️ **[Fast](https://ovld.readthedocs.io/en/latest/compare/#results):** ovld is the fastest multiple dispatch library around, by some margin.
|
24
24
|
* 🚀 [**Variants**](https://ovld.readthedocs.io/en/latest/usage/#variants), [**mixins**](https://ovld.readthedocs.io/en/latest/usage/#mixins) and [**medleys**](https://ovld.readthedocs.io/en/latest/medley) of functions and methods.
|
25
|
-
* 🦄 **[
|
25
|
+
* 🦄 **[Value-based dispatch](https://ovld.readthedocs.io/en/latest/dependent/):** Overloaded functions can depend on more than argument types: they can depend on actual values.
|
26
26
|
* 🔑 **[Extensive](https://ovld.readthedocs.io/en/latest/usage/#keyword-arguments):** Dispatch on functions, methods, positional arguments and even keyword arguments (with some restrictions).
|
27
27
|
* ⚙️ **[Codegen](https://ovld.readthedocs.io/en/latest/codegen/):** (Experimental) For advanced use cases, you can generate custom code for overloads.
|
28
28
|
|
29
|
+
Install with `pip install ovld`
|
30
|
+
|
31
|
+
|
29
32
|
## Example
|
30
33
|
|
31
|
-
|
34
|
+
Define one version of your function for each type signature you want to support. `ovld` supports all basic types, plus literals and value-dependent types such as `Regexp`.
|
35
|
+
|
36
|
+
```python
|
37
|
+
from ovld import ovld
|
38
|
+
from ovld.dependent import Regexp
|
39
|
+
from typing import Literal
|
40
|
+
|
41
|
+
@ovld
|
42
|
+
def f(x: str):
|
43
|
+
return f"The string {x!r}"
|
44
|
+
|
45
|
+
@ovld
|
46
|
+
def f(x: int):
|
47
|
+
return f"The number {x}"
|
48
|
+
|
49
|
+
@ovld
|
50
|
+
def f(x: int, y: int):
|
51
|
+
return "Two numbers!"
|
52
|
+
|
53
|
+
@ovld
|
54
|
+
def f(x: Literal[0]):
|
55
|
+
return "zero"
|
56
|
+
|
57
|
+
@ovld
|
58
|
+
def f(x: Regexp[r"^X"]):
|
59
|
+
return "A string that starts with X"
|
60
|
+
|
61
|
+
assert f("hello") == "The string 'hello'"
|
62
|
+
assert f(3) == "The number 3"
|
63
|
+
assert f(1, 2) == "Two numbers!"
|
64
|
+
assert f(0) == "zero"
|
65
|
+
assert f("XSECRET") == "A string that starts with X"
|
66
|
+
```
|
67
|
+
|
68
|
+
|
69
|
+
## Recursive example
|
70
|
+
|
71
|
+
`ovld` shines particularly with recursive definitions, for example tree maps or serialization. Here we define a function that recursively adds lists of lists and integers:
|
32
72
|
|
33
73
|
```python
|
34
74
|
from ovld import ovld, recurse
|
@@ -38,18 +78,19 @@ def add(x: list, y: list):
|
|
38
78
|
return [recurse(a, b) for a, b in zip(x, y)]
|
39
79
|
|
40
80
|
@ovld
|
41
|
-
def add(x:
|
42
|
-
return
|
81
|
+
def add(x: list, y: int):
|
82
|
+
return [recurse(a, y) for a in x]
|
43
83
|
|
44
84
|
@ovld
|
45
|
-
def add(x:
|
46
|
-
return
|
85
|
+
def add(x: int, y: list):
|
86
|
+
return [recurse(x, a) for a in y]
|
47
87
|
|
48
88
|
@ovld
|
49
|
-
def add(x:
|
89
|
+
def add(x: int, y: int):
|
50
90
|
return x + y
|
51
91
|
|
52
92
|
assert add([1, 2], [3, 4]) == [4, 6]
|
93
|
+
assert add([1, 2, [3]], 7) == [8, 9, [10]]
|
53
94
|
```
|
54
95
|
|
55
96
|
The `recurse` function is special: it will recursively call the current ovld object. You may ask: how is it different from simply calling `add`? The difference is that if you create a *variant* of `add`, `recurse` will automatically call the variant.
|
@@ -63,7 +104,7 @@ A *variant* of an `ovld` is a copy of the `ovld`, with some methods added or cha
|
|
63
104
|
|
64
105
|
```python
|
65
106
|
@add.variant
|
66
|
-
def mul(x:
|
107
|
+
def mul(x: int, y: int):
|
67
108
|
return x * y
|
68
109
|
|
69
110
|
assert mul([1, 2], [3, 4]) == [3, 8]
|
@@ -92,9 +133,9 @@ assert f(10) == 121
|
|
92
133
|
|
93
134
|
Both definitions above have the same type signature, but since the first has higher priority, that is the one that will be called.
|
94
135
|
|
95
|
-
However, that does not mean there is no way to call the second one. Indeed, when the first function calls the special function `call_next(x + 1)`, it will call the next function in
|
136
|
+
However, that does not mean there is no way to call the second one. Indeed, when the first function calls the special function `call_next(x + 1)`, it will call the next function in line, in order of priority and specificity.
|
96
137
|
|
97
|
-
The pattern you see above is how you may wrap each call with some generic behavior. For instance, if you did something like
|
138
|
+
The pattern you see above is how you may wrap each call with some generic behavior. For instance, if you did something like this:
|
98
139
|
|
99
140
|
```python
|
100
141
|
@f.variant(priority=1000)
|
@@ -103,12 +144,12 @@ def f2(x: object)
|
|
103
144
|
return call_next(x)
|
104
145
|
```
|
105
146
|
|
106
|
-
|
147
|
+
The above is effectively a clone of `f` that traces every call. Useful for debugging.
|
107
148
|
|
108
149
|
|
109
150
|
## Dependent types
|
110
151
|
|
111
|
-
A dependent type is a type that depends on a value.
|
152
|
+
A dependent type is a type that depends on a value. This enables dispatching based on the actual value of an argument. The simplest example of a dependent type is `typing.Literal[value]`, which matches one single value. `ovld` also supports `Dependent[bound, check]` for arbitrary checks. For example, this definition of factorial:
|
112
153
|
|
113
154
|
```python
|
114
155
|
from typing import Literal
|
@@ -9,13 +9,53 @@ With ovld, you can write a version of the same function for every type signature
|
|
9
9
|
|
10
10
|
* ⚡️ **[Fast](https://ovld.readthedocs.io/en/latest/compare/#results):** ovld is the fastest multiple dispatch library around, by some margin.
|
11
11
|
* 🚀 [**Variants**](https://ovld.readthedocs.io/en/latest/usage/#variants), [**mixins**](https://ovld.readthedocs.io/en/latest/usage/#mixins) and [**medleys**](https://ovld.readthedocs.io/en/latest/medley) of functions and methods.
|
12
|
-
* 🦄 **[
|
12
|
+
* 🦄 **[Value-based dispatch](https://ovld.readthedocs.io/en/latest/dependent/):** Overloaded functions can depend on more than argument types: they can depend on actual values.
|
13
13
|
* 🔑 **[Extensive](https://ovld.readthedocs.io/en/latest/usage/#keyword-arguments):** Dispatch on functions, methods, positional arguments and even keyword arguments (with some restrictions).
|
14
14
|
* ⚙️ **[Codegen](https://ovld.readthedocs.io/en/latest/codegen/):** (Experimental) For advanced use cases, you can generate custom code for overloads.
|
15
15
|
|
16
|
+
Install with `pip install ovld`
|
17
|
+
|
18
|
+
|
16
19
|
## Example
|
17
20
|
|
18
|
-
|
21
|
+
Define one version of your function for each type signature you want to support. `ovld` supports all basic types, plus literals and value-dependent types such as `Regexp`.
|
22
|
+
|
23
|
+
```python
|
24
|
+
from ovld import ovld
|
25
|
+
from ovld.dependent import Regexp
|
26
|
+
from typing import Literal
|
27
|
+
|
28
|
+
@ovld
|
29
|
+
def f(x: str):
|
30
|
+
return f"The string {x!r}"
|
31
|
+
|
32
|
+
@ovld
|
33
|
+
def f(x: int):
|
34
|
+
return f"The number {x}"
|
35
|
+
|
36
|
+
@ovld
|
37
|
+
def f(x: int, y: int):
|
38
|
+
return "Two numbers!"
|
39
|
+
|
40
|
+
@ovld
|
41
|
+
def f(x: Literal[0]):
|
42
|
+
return "zero"
|
43
|
+
|
44
|
+
@ovld
|
45
|
+
def f(x: Regexp[r"^X"]):
|
46
|
+
return "A string that starts with X"
|
47
|
+
|
48
|
+
assert f("hello") == "The string 'hello'"
|
49
|
+
assert f(3) == "The number 3"
|
50
|
+
assert f(1, 2) == "Two numbers!"
|
51
|
+
assert f(0) == "zero"
|
52
|
+
assert f("XSECRET") == "A string that starts with X"
|
53
|
+
```
|
54
|
+
|
55
|
+
|
56
|
+
## Recursive example
|
57
|
+
|
58
|
+
`ovld` shines particularly with recursive definitions, for example tree maps or serialization. Here we define a function that recursively adds lists of lists and integers:
|
19
59
|
|
20
60
|
```python
|
21
61
|
from ovld import ovld, recurse
|
@@ -25,18 +65,19 @@ def add(x: list, y: list):
|
|
25
65
|
return [recurse(a, b) for a, b in zip(x, y)]
|
26
66
|
|
27
67
|
@ovld
|
28
|
-
def add(x:
|
29
|
-
return
|
68
|
+
def add(x: list, y: int):
|
69
|
+
return [recurse(a, y) for a in x]
|
30
70
|
|
31
71
|
@ovld
|
32
|
-
def add(x:
|
33
|
-
return
|
72
|
+
def add(x: int, y: list):
|
73
|
+
return [recurse(x, a) for a in y]
|
34
74
|
|
35
75
|
@ovld
|
36
|
-
def add(x:
|
76
|
+
def add(x: int, y: int):
|
37
77
|
return x + y
|
38
78
|
|
39
79
|
assert add([1, 2], [3, 4]) == [4, 6]
|
80
|
+
assert add([1, 2, [3]], 7) == [8, 9, [10]]
|
40
81
|
```
|
41
82
|
|
42
83
|
The `recurse` function is special: it will recursively call the current ovld object. You may ask: how is it different from simply calling `add`? The difference is that if you create a *variant* of `add`, `recurse` will automatically call the variant.
|
@@ -50,7 +91,7 @@ A *variant* of an `ovld` is a copy of the `ovld`, with some methods added or cha
|
|
50
91
|
|
51
92
|
```python
|
52
93
|
@add.variant
|
53
|
-
def mul(x:
|
94
|
+
def mul(x: int, y: int):
|
54
95
|
return x * y
|
55
96
|
|
56
97
|
assert mul([1, 2], [3, 4]) == [3, 8]
|
@@ -79,9 +120,9 @@ assert f(10) == 121
|
|
79
120
|
|
80
121
|
Both definitions above have the same type signature, but since the first has higher priority, that is the one that will be called.
|
81
122
|
|
82
|
-
However, that does not mean there is no way to call the second one. Indeed, when the first function calls the special function `call_next(x + 1)`, it will call the next function in
|
123
|
+
However, that does not mean there is no way to call the second one. Indeed, when the first function calls the special function `call_next(x + 1)`, it will call the next function in line, in order of priority and specificity.
|
83
124
|
|
84
|
-
The pattern you see above is how you may wrap each call with some generic behavior. For instance, if you did something like
|
125
|
+
The pattern you see above is how you may wrap each call with some generic behavior. For instance, if you did something like this:
|
85
126
|
|
86
127
|
```python
|
87
128
|
@f.variant(priority=1000)
|
@@ -90,12 +131,12 @@ def f2(x: object)
|
|
90
131
|
return call_next(x)
|
91
132
|
```
|
92
133
|
|
93
|
-
|
134
|
+
The above is effectively a clone of `f` that traces every call. Useful for debugging.
|
94
135
|
|
95
136
|
|
96
137
|
## Dependent types
|
97
138
|
|
98
|
-
A dependent type is a type that depends on a value.
|
139
|
+
A dependent type is a type that depends on a value. This enables dispatching based on the actual value of an argument. The simplest example of a dependent type is `typing.Literal[value]`, which matches one single value. `ovld` also supports `Dependent[bound, check]` for arbitrary checks. For example, this definition of factorial:
|
99
140
|
|
100
141
|
```python
|
101
142
|
from typing import Literal
|
@@ -12,7 +12,44 @@ With ovld, you can write a version of the same function for every type signature
|
|
12
12
|
|
13
13
|
## Example
|
14
14
|
|
15
|
-
|
15
|
+
Define one version of your function for each type signature you want to support. `ovld` supports all basic types, plus literals and value-dependent types such as `Regexp`.
|
16
|
+
|
17
|
+
```python
|
18
|
+
from ovld import ovld
|
19
|
+
from ovld.dependent import Regexp
|
20
|
+
from typing import Literal
|
21
|
+
|
22
|
+
@ovld
|
23
|
+
def f(x: str):
|
24
|
+
return f"The string {x!r}"
|
25
|
+
|
26
|
+
@ovld
|
27
|
+
def f(x: int):
|
28
|
+
return f"The number {x}"
|
29
|
+
|
30
|
+
@ovld
|
31
|
+
def f(x: int, y: int):
|
32
|
+
return "Two numbers!"
|
33
|
+
|
34
|
+
@ovld
|
35
|
+
def f(x: Literal[0]):
|
36
|
+
return "zero"
|
37
|
+
|
38
|
+
@ovld
|
39
|
+
def f(x: Regexp[r"^X"]):
|
40
|
+
return "A string that starts with X"
|
41
|
+
|
42
|
+
assert f("hello") == "The string 'hello'"
|
43
|
+
assert f(3) == "The number 3"
|
44
|
+
assert f(1, 2) == "Two numbers!"
|
45
|
+
assert f(0) == "zero"
|
46
|
+
assert f("XSECRET") == "A string that starts with X"
|
47
|
+
```
|
48
|
+
|
49
|
+
|
50
|
+
## Recursive example
|
51
|
+
|
52
|
+
`ovld` shines particularly with recursive definitions, for example tree maps or serialization. Here we define a function that recursively adds lists of lists and integers:
|
16
53
|
|
17
54
|
```python
|
18
55
|
from ovld import ovld, recurse
|
@@ -22,16 +59,19 @@ def add(x: list, y: list):
|
|
22
59
|
return [recurse(a, b) for a, b in zip(x, y)]
|
23
60
|
|
24
61
|
@ovld
|
25
|
-
def add(x:
|
26
|
-
return
|
62
|
+
def add(x: list, y: int):
|
63
|
+
return [recurse(a, y) for a in x]
|
27
64
|
|
28
65
|
@ovld
|
29
|
-
def add(x:
|
30
|
-
return
|
66
|
+
def add(x: int, y: list):
|
67
|
+
return [recurse(x, a) for a in y]
|
31
68
|
|
32
69
|
@ovld
|
33
|
-
def add(x:
|
70
|
+
def add(x: int, y: int):
|
34
71
|
return x + y
|
72
|
+
|
73
|
+
assert add([1, 2], [3, 4]) == [4, 6]
|
74
|
+
assert add([1, 2, [3]], 7) == [8, 9, [10]]
|
35
75
|
```
|
36
76
|
|
37
77
|
The **`recurse`** function is special: it will recursively call the current ovld object. You may ask: how is it different from simply calling `add`? The difference is that if you create a *variant* of `add`, `recurse` will automatically call the variant:
|
@@ -43,7 +83,7 @@ A **variant** of an `ovld` is a copy of the `ovld`, with some methods added or c
|
|
43
83
|
|
44
84
|
```python
|
45
85
|
@add.variant
|
46
|
-
def mul(x:
|
86
|
+
def mul(x: int, y: int):
|
47
87
|
return x * y
|
48
88
|
|
49
89
|
assert mul([1, 2], [3, 4]) == [3, 8]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "ovld"
|
3
|
-
version = "0.5.
|
3
|
+
version = "0.5.7"
|
4
4
|
description = "Overloading Python functions"
|
5
5
|
authors = [
|
6
6
|
{ name = "Olivier Breuleux", email = "breuleux@gmail.com" }
|
@@ -44,6 +44,8 @@ line-length = 95
|
|
44
44
|
[tool.ruff.lint]
|
45
45
|
extend-select = ["I"]
|
46
46
|
ignore = ["E241", "F722", "E501", "E203", "F811", "F821"]
|
47
|
+
# Ignore these for now, ruff reports SyntaxError because they use Python 3.10+ features
|
48
|
+
exclude = ["benchmarks/test_calc.py", "benchmarks/test_tweaknum.py"]
|
47
49
|
|
48
50
|
[tool.pytest.ini_options]
|
49
51
|
minversion = "6.0"
|
@@ -45,6 +45,7 @@ def bootstrap_dispatch(ov, name):
|
|
45
45
|
dispatch.register = ov.register
|
46
46
|
dispatch.resolve_for_values = ov.resolve_for_values
|
47
47
|
dispatch.resolve = ov.resolve
|
48
|
+
dispatch.resolve_all = ov.resolve_all
|
48
49
|
dispatch.copy = ov.copy
|
49
50
|
dispatch.variant = ov.variant
|
50
51
|
dispatch.display_methods = ov.display_methods
|
@@ -250,6 +251,11 @@ class Ovld:
|
|
250
251
|
else:
|
251
252
|
return self.map[args]
|
252
253
|
|
254
|
+
def resolve_all(self, *args, **kwargs):
|
255
|
+
"""Yield all methods that match the arguments, in priority order."""
|
256
|
+
self.ensure_compiled()
|
257
|
+
return self.map.resolve_all(*args, **kwargs)
|
258
|
+
|
253
259
|
def register_signature(self, sig, orig_fn):
|
254
260
|
"""Register a function for the given signature."""
|
255
261
|
fn = adapt_function(orig_fn, self, f"{self.__name__}[{sigstring(sig.types)}]")
|
@@ -15,22 +15,15 @@ from .types import (
|
|
15
15
|
Intersection,
|
16
16
|
Order,
|
17
17
|
clsstring,
|
18
|
-
get_args,
|
19
18
|
normalize_type,
|
20
19
|
subclasscheck,
|
21
20
|
typeorder,
|
22
21
|
)
|
23
22
|
|
24
23
|
|
25
|
-
def is_dependent(t):
|
26
|
-
if isinstance(t, DependentType):
|
27
|
-
return True
|
28
|
-
elif any(is_dependent(subt) for subt in get_args(t)):
|
29
|
-
return True
|
30
|
-
return False
|
31
|
-
|
32
|
-
|
33
24
|
class DependentType(type):
|
25
|
+
__dependent__ = True
|
26
|
+
|
34
27
|
exclusive_type = False
|
35
28
|
keyable_type = False
|
36
29
|
bound_is_name = False
|
@@ -3,7 +3,7 @@ from enum import Enum
|
|
3
3
|
from graphlib import TopologicalSorter
|
4
4
|
from typing import get_args, get_origin
|
5
5
|
|
6
|
-
from .utils import UnionTypes
|
6
|
+
from .utils import UnionTypes, is_dependent
|
7
7
|
|
8
8
|
|
9
9
|
class Order(Enum):
|
@@ -123,6 +123,11 @@ def subclasscheck(t1, t2):
|
|
123
123
|
):
|
124
124
|
return result
|
125
125
|
|
126
|
+
if is_dependent(t2):
|
127
|
+
# t2's instancecheck could return anything, and unless it defines
|
128
|
+
# __is_supertype__ or __is_subtype__ the bound devolves to object
|
129
|
+
return True
|
130
|
+
|
126
131
|
if t2 in UnionTypes:
|
127
132
|
return isinstance(t1, t2)
|
128
133
|
|
@@ -12,7 +12,7 @@ from .codegen import (
|
|
12
12
|
rename_function,
|
13
13
|
transfer_function,
|
14
14
|
)
|
15
|
-
from .utils import MISSING, NameDatabase, SpecialForm, UsageError, subtler_type
|
15
|
+
from .utils import MISSING, NameDatabase, SpecialForm, UsageError, is_dependent, subtler_type
|
16
16
|
|
17
17
|
recurse = SpecialForm("recurse")
|
18
18
|
call_next = SpecialForm("call_next")
|
@@ -160,8 +160,6 @@ def generate_dispatch(ov, arganal):
|
|
160
160
|
|
161
161
|
|
162
162
|
def generate_dependent_dispatch(tup, handlers, next_call, slf, name, err, nerr):
|
163
|
-
from .dependent import is_dependent
|
164
|
-
|
165
163
|
def to_dict(tup):
|
166
164
|
return dict(
|
167
165
|
entry if isinstance(entry, tuple) else (i, entry) for i, entry in enumerate(tup)
|
@@ -1,12 +1,13 @@
|
|
1
1
|
import inspect
|
2
2
|
import math
|
3
3
|
from dataclasses import dataclass
|
4
|
+
from functools import partial
|
4
5
|
from itertools import count
|
5
6
|
from types import CodeType
|
6
7
|
|
7
8
|
from .mro import sort_types
|
8
9
|
from .recode import generate_dependent_dispatch
|
9
|
-
from .utils import MISSING, CodegenInProgress, subtler_type
|
10
|
+
from .utils import MISSING, CodegenInProgress, is_dependent, subtler_type
|
10
11
|
|
11
12
|
|
12
13
|
class TypeMap(dict):
|
@@ -213,8 +214,6 @@ class MultiTypeMap(dict):
|
|
213
214
|
sig: A Signature object.
|
214
215
|
handler: A function to handle the tuple.
|
215
216
|
"""
|
216
|
-
from .dependent import is_dependent
|
217
|
-
|
218
217
|
self.clear()
|
219
218
|
|
220
219
|
obj_t_tup = sig.types
|
@@ -250,9 +249,7 @@ class MultiTypeMap(dict):
|
|
250
249
|
co = h.__code__
|
251
250
|
print(f"{'':{width - 2}} @ {co.co_filename}:{co.co_firstlineno}")
|
252
251
|
|
253
|
-
def
|
254
|
-
from .dependent import is_dependent
|
255
|
-
|
252
|
+
def _resolve_all_helper(self, *args, **kwargs):
|
256
253
|
def dependent_match(tup, args):
|
257
254
|
for t, a in zip(tup, args):
|
258
255
|
if isinstance(t, tuple):
|
@@ -262,21 +259,35 @@ class MultiTypeMap(dict):
|
|
262
259
|
return False
|
263
260
|
return True
|
264
261
|
|
265
|
-
message = "No method will be called."
|
266
262
|
argt = [
|
267
263
|
*map(subtler_type, args),
|
268
264
|
*[(k, subtler_type(v)) for k, v in kwargs.items()],
|
269
265
|
]
|
270
|
-
finished = False
|
271
|
-
rank = 1
|
272
266
|
for grp in self.mro(tuple(argt)):
|
273
|
-
|
274
|
-
|
275
|
-
|
267
|
+
yield [
|
268
|
+
(
|
269
|
+
c,
|
270
|
+
dependent_match(
|
271
|
+
self.type_tuples[c.base_handler], [*args, *kwargs.items()]
|
272
|
+
),
|
273
|
+
)
|
276
274
|
for c in grp
|
277
275
|
]
|
278
|
-
|
279
|
-
|
276
|
+
|
277
|
+
def resolve_all(self, *args, **kwargs):
|
278
|
+
for grp in self._resolve_all_helper(*args, **kwargs):
|
279
|
+
for c, m in grp:
|
280
|
+
if m:
|
281
|
+
yield partial(c.handler, *args, **kwargs)
|
282
|
+
|
283
|
+
def display_resolution(self, *args, **kwargs):
|
284
|
+
message = "No method will be called."
|
285
|
+
finished = False
|
286
|
+
rank = 1
|
287
|
+
for grp in self._resolve_all_helper(*args, **kwargs):
|
288
|
+
grp.sort(key=lambda x: x[0].handler.__name__)
|
289
|
+
ambiguous = len([m for _, m in grp if m]) > 1
|
290
|
+
for c, m in grp:
|
280
291
|
handler = c.handler
|
281
292
|
color = "\033[0m"
|
282
293
|
if finished:
|
@@ -10,14 +10,9 @@ from .codegen import Code
|
|
10
10
|
from .mro import Order, TypeRelationship, subclasscheck, typeorder
|
11
11
|
from .recode import generate_checking_code
|
12
12
|
from .typemap import TypeMap
|
13
|
-
from .utils import UnionType, UnionTypes, UsageError, clsstring
|
13
|
+
from .utils import UnionType, UnionTypes, UsageError, clsstring, get_args
|
14
14
|
|
15
|
-
|
16
|
-
def get_args(tp):
|
17
|
-
args = getattr(tp, "__args__", None)
|
18
|
-
if not isinstance(args, tuple):
|
19
|
-
args = ()
|
20
|
-
return args
|
15
|
+
NoneType = type(None)
|
21
16
|
|
22
17
|
|
23
18
|
def eval_annotation(t, ctx, locals, catch=False):
|
@@ -90,6 +85,8 @@ class TypeNormalizer:
|
|
90
85
|
raise UsageError(
|
91
86
|
f"Dependent type {t} has not been given a type bound. Please use Dependent[<bound>, {t}] instead."
|
92
87
|
)
|
88
|
+
elif t is None:
|
89
|
+
return NoneType
|
93
90
|
else:
|
94
91
|
return t
|
95
92
|
|
@@ -103,6 +100,8 @@ def _(self, t, fn):
|
|
103
100
|
|
104
101
|
|
105
102
|
class MetaMC(type):
|
103
|
+
__dependent__ = False
|
104
|
+
|
106
105
|
def __new__(T, name, handler):
|
107
106
|
return super().__new__(T, name, (), {"_handler": handler})
|
108
107
|
|
@@ -4,6 +4,7 @@ import builtins
|
|
4
4
|
import functools
|
5
5
|
import re
|
6
6
|
import typing
|
7
|
+
from abc import ABCMeta
|
7
8
|
from itertools import count
|
8
9
|
|
9
10
|
_builtins_dict = vars(builtins)
|
@@ -183,3 +184,31 @@ class NameDatabase:
|
|
183
184
|
return name
|
184
185
|
|
185
186
|
__getitem__ = get
|
187
|
+
|
188
|
+
|
189
|
+
def get_args(tp):
|
190
|
+
args = getattr(tp, "__args__", None)
|
191
|
+
if not isinstance(args, tuple):
|
192
|
+
args = ()
|
193
|
+
return args
|
194
|
+
|
195
|
+
|
196
|
+
_standard_instancechecks = {
|
197
|
+
type.__instancecheck__,
|
198
|
+
GenericAlias.__instancecheck__,
|
199
|
+
type(list[object]).__instancecheck__,
|
200
|
+
ABCMeta.__instancecheck__,
|
201
|
+
type(typing.Protocol).__instancecheck__,
|
202
|
+
}
|
203
|
+
|
204
|
+
|
205
|
+
def is_dependent(t):
|
206
|
+
if any(is_dependent(subt) for subt in get_args(t)):
|
207
|
+
return True
|
208
|
+
elif hasattr(t, "__dependent__"):
|
209
|
+
return t.__dependent__
|
210
|
+
elif not isinstance(t, type):
|
211
|
+
return False
|
212
|
+
elif type(t).__instancecheck__ not in _standard_instancechecks:
|
213
|
+
return True
|
214
|
+
return False
|
@@ -0,0 +1 @@
|
|
1
|
+
version = "0.5.7"
|
@@ -350,3 +350,31 @@ def test_keyed_plus_other():
|
|
350
350
|
assert f(0, 50) == "yes"
|
351
351
|
assert f(3, 50) == "yes"
|
352
352
|
assert f(0, 150) == "no"
|
353
|
+
|
354
|
+
|
355
|
+
class Lettered(type):
|
356
|
+
def __instancecheck__(cls, x):
|
357
|
+
return cls.letter in (getattr(x, "__name__", None) or str(x))
|
358
|
+
|
359
|
+
|
360
|
+
LetterF = Lettered("LetterF", (), {"letter": "f"})
|
361
|
+
LetterX = Lettered("LetterX", (), {"letter": "x"})
|
362
|
+
|
363
|
+
|
364
|
+
def test_arbitrary_instancecheck():
|
365
|
+
@ovld
|
366
|
+
def f(x: LetterX):
|
367
|
+
return "X-TREME"
|
368
|
+
|
369
|
+
@ovld
|
370
|
+
def f(x: LetterF):
|
371
|
+
return "fudged"
|
372
|
+
|
373
|
+
@ovld
|
374
|
+
def f(x: object):
|
375
|
+
return False
|
376
|
+
|
377
|
+
assert f(next) == "X-TREME"
|
378
|
+
assert f("wow") is False
|
379
|
+
assert f("extreme") == "X-TREME"
|
380
|
+
assert f(filter) == "fudged"
|
@@ -19,7 +19,7 @@ from ovld import (
|
|
19
19
|
recurse,
|
20
20
|
resolve,
|
21
21
|
)
|
22
|
-
from ovld.dependent import Dependent, Equals, StartsWith
|
22
|
+
from ovld.dependent import Dependent, Equals, Regexp, StartsWith
|
23
23
|
from ovld.types import UnionTypes
|
24
24
|
from ovld.utils import MISSING, UsageError
|
25
25
|
|
@@ -324,6 +324,19 @@ def test_optional():
|
|
324
324
|
assert f(1.0) == "f"
|
325
325
|
|
326
326
|
|
327
|
+
def test_none():
|
328
|
+
@ovld
|
329
|
+
def f(x: None):
|
330
|
+
return "null"
|
331
|
+
|
332
|
+
@ovld
|
333
|
+
def f(x: object):
|
334
|
+
return "X"
|
335
|
+
|
336
|
+
assert f(None) == "null"
|
337
|
+
assert f(123) == "X"
|
338
|
+
|
339
|
+
|
327
340
|
def test_abstract_types():
|
328
341
|
from collections.abc import Iterable
|
329
342
|
|
@@ -1678,6 +1691,33 @@ def test_keywords_recurse():
|
|
1678
1691
|
assert f([1, 2, 3], factor=3) == [3, 6, 9]
|
1679
1692
|
|
1680
1693
|
|
1694
|
+
def test_resolve_all():
|
1695
|
+
@ovld
|
1696
|
+
def f(x: str):
|
1697
|
+
return "str"
|
1698
|
+
|
1699
|
+
@ovld(priority=7)
|
1700
|
+
def f(x: str):
|
1701
|
+
return "MAX"
|
1702
|
+
|
1703
|
+
@ovld(priority=1)
|
1704
|
+
def f(x: Regexp["^hello"]):
|
1705
|
+
return "hello"
|
1706
|
+
|
1707
|
+
@ovld
|
1708
|
+
def f(x: Regexp["world$"]):
|
1709
|
+
return "world"
|
1710
|
+
|
1711
|
+
@ovld
|
1712
|
+
def f(x: int):
|
1713
|
+
return "int"
|
1714
|
+
|
1715
|
+
assert [t() for t in f.resolve_all("hello world")] == ["MAX", "hello", "world", "str"]
|
1716
|
+
assert [t() for t in f.resolve_all("hello, you!")] == ["MAX", "hello", "str"]
|
1717
|
+
assert [t() for t in f.resolve_all(73)] == ["int"]
|
1718
|
+
assert [t() for t in f.resolve_all(object())] == []
|
1719
|
+
|
1720
|
+
|
1681
1721
|
def test_passing_types_to_normal_func():
|
1682
1722
|
@ovld
|
1683
1723
|
def f(x):
|