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.
Files changed (69) hide show
  1. {ovld-0.5.5 → ovld-0.5.7}/PKG-INFO +54 -13
  2. {ovld-0.5.5 → ovld-0.5.7}/README.md +53 -12
  3. {ovld-0.5.5 → ovld-0.5.7}/docs/index.md +47 -7
  4. {ovld-0.5.5 → ovld-0.5.7}/pyproject.toml +3 -1
  5. {ovld-0.5.5 → ovld-0.5.7}/src/ovld/core.py +6 -0
  6. {ovld-0.5.5 → ovld-0.5.7}/src/ovld/dependent.py +2 -9
  7. {ovld-0.5.5 → ovld-0.5.7}/src/ovld/mro.py +6 -1
  8. {ovld-0.5.5 → ovld-0.5.7}/src/ovld/recode.py +1 -3
  9. {ovld-0.5.5 → ovld-0.5.7}/src/ovld/typemap.py +25 -14
  10. {ovld-0.5.5 → ovld-0.5.7}/src/ovld/types.py +6 -7
  11. {ovld-0.5.5 → ovld-0.5.7}/src/ovld/utils.py +29 -0
  12. ovld-0.5.7/src/ovld/version.py +1 -0
  13. {ovld-0.5.5 → ovld-0.5.7}/tests/test_dependent.py +28 -0
  14. {ovld-0.5.5 → ovld-0.5.7}/tests/test_ovld.py +41 -1
  15. {ovld-0.5.5 → ovld-0.5.7}/uv.lock +333 -327
  16. ovld-0.5.5/src/ovld/version.py +0 -1
  17. {ovld-0.5.5 → ovld-0.5.7}/.github/workflows/publish.yml +0 -0
  18. {ovld-0.5.5 → ovld-0.5.7}/.github/workflows/python-package.yml +0 -0
  19. {ovld-0.5.5 → ovld-0.5.7}/.gitignore +0 -0
  20. {ovld-0.5.5 → ovld-0.5.7}/.python-version +0 -0
  21. {ovld-0.5.5 → ovld-0.5.7}/.readthedocs.yaml +0 -0
  22. {ovld-0.5.5 → ovld-0.5.7}/LICENSE +0 -0
  23. {ovld-0.5.5 → ovld-0.5.7}/benchmarks/__init__.py +0 -0
  24. {ovld-0.5.5 → ovld-0.5.7}/benchmarks/common.py +0 -0
  25. {ovld-0.5.5 → ovld-0.5.7}/benchmarks/conftest.py +0 -0
  26. {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_add.py +0 -0
  27. {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_ast.py +0 -0
  28. {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_calc.py +0 -0
  29. {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_fib.py +0 -0
  30. {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_multer.py +0 -0
  31. {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_regexp.py +0 -0
  32. {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_trivial.py +0 -0
  33. {ovld-0.5.5 → ovld-0.5.7}/benchmarks/test_tweaknum.py +0 -0
  34. {ovld-0.5.5 → ovld-0.5.7}/docs/codegen.md +0 -0
  35. {ovld-0.5.5 → ovld-0.5.7}/docs/compare.md +0 -0
  36. {ovld-0.5.5 → ovld-0.5.7}/docs/dependent.md +0 -0
  37. {ovld-0.5.5 → ovld-0.5.7}/docs/features.md +0 -0
  38. {ovld-0.5.5 → ovld-0.5.7}/docs/medley.md +0 -0
  39. {ovld-0.5.5 → ovld-0.5.7}/docs/types.md +0 -0
  40. {ovld-0.5.5 → ovld-0.5.7}/docs/usage.md +0 -0
  41. {ovld-0.5.5 → ovld-0.5.7}/mkdocs.yml +0 -0
  42. {ovld-0.5.5 → ovld-0.5.7}/src/ovld/__init__.py +0 -0
  43. {ovld-0.5.5 → ovld-0.5.7}/src/ovld/abc.py +0 -0
  44. {ovld-0.5.5 → ovld-0.5.7}/src/ovld/codegen.py +0 -0
  45. {ovld-0.5.5 → ovld-0.5.7}/src/ovld/medley.py +0 -0
  46. {ovld-0.5.5 → ovld-0.5.7}/src/ovld/py.typed +0 -0
  47. {ovld-0.5.5 → ovld-0.5.7}/src/ovld/signatures.py +0 -0
  48. {ovld-0.5.5 → ovld-0.5.7}/tests/__init__.py +0 -0
  49. {ovld-0.5.5 → ovld-0.5.7}/tests/modules/gingerbread.py +0 -0
  50. {ovld-0.5.5 → ovld-0.5.7}/tests/test_abc.py +0 -0
  51. {ovld-0.5.5 → ovld-0.5.7}/tests/test_codegen/test_dataclass_gen.txt +0 -0
  52. {ovld-0.5.5 → ovld-0.5.7}/tests/test_codegen/test_method.txt +0 -0
  53. {ovld-0.5.5 → ovld-0.5.7}/tests/test_codegen/test_method_metaclass.txt +0 -0
  54. {ovld-0.5.5 → ovld-0.5.7}/tests/test_codegen/test_method_per_instance.txt +0 -0
  55. {ovld-0.5.5 → ovld-0.5.7}/tests/test_codegen/test_simple.txt +0 -0
  56. {ovld-0.5.5 → ovld-0.5.7}/tests/test_codegen/test_variant_generation.txt +0 -0
  57. {ovld-0.5.5 → ovld-0.5.7}/tests/test_codegen.py +0 -0
  58. {ovld-0.5.5 → ovld-0.5.7}/tests/test_examples.py +0 -0
  59. {ovld-0.5.5 → ovld-0.5.7}/tests/test_global.py +0 -0
  60. {ovld-0.5.5 → ovld-0.5.7}/tests/test_medley.py +0 -0
  61. {ovld-0.5.5 → ovld-0.5.7}/tests/test_mro.py +0 -0
  62. {ovld-0.5.5 → ovld-0.5.7}/tests/test_ovld/test_display.txt +0 -0
  63. {ovld-0.5.5 → ovld-0.5.7}/tests/test_ovld/test_display_more.txt +0 -0
  64. {ovld-0.5.5 → ovld-0.5.7}/tests/test_ovld/test_doc.txt +0 -0
  65. {ovld-0.5.5 → ovld-0.5.7}/tests/test_ovld/test_doc2.txt +0 -0
  66. {ovld-0.5.5 → ovld-0.5.7}/tests/test_ovld/test_method_doc.txt +0 -0
  67. {ovld-0.5.5 → ovld-0.5.7}/tests/test_typemap.py +0 -0
  68. {ovld-0.5.5 → ovld-0.5.7}/tests/test_types.py +0 -0
  69. {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.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
- * 🦄 **[Dependent types](https://ovld.readthedocs.io/en/latest/dependent/):** Overloaded functions can depend on more than argument types: they can depend on actual values.
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
- Here's a function that recursively adds lists, tuples and dictionaries:
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: tuple, y: tuple):
42
- return tuple(recurse(a, b) for a, b in zip(x, y))
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: dict, y: dict):
46
- return {k: recurse(v, y[k]) for k, v in x.items()}
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: object, y: object):
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: object, y: object):
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 the list below itself.
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 that:
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
- You would effectively be creating a clone of `f` that traces every call.
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. `ovld` supports this, either through `Literal[value]` or `Dependent[bound, check]`. For example, this definition of factorial:
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
- * 🦄 **[Dependent types](https://ovld.readthedocs.io/en/latest/dependent/):** Overloaded functions can depend on more than argument types: they can depend on actual values.
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
- Here's a function that recursively adds lists, tuples and dictionaries:
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: tuple, y: tuple):
29
- return tuple(recurse(a, b) for a, b in zip(x, y))
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: dict, y: dict):
33
- return {k: recurse(v, y[k]) for k, v in x.items()}
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: object, y: object):
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: object, y: object):
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 the list below itself.
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 that:
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
- You would effectively be creating a clone of `f` that traces every call.
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. `ovld` supports this, either through `Literal[value]` or `Dependent[bound, check]`. For example, this definition of factorial:
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
- Here's a function that adds lists, tuples and dictionaries:
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: tuple, y: tuple):
26
- return tuple(recurse(a, b) for a, b in zip(x, y))
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: dict, y: dict):
30
- return {k: recurse(v, y[k]) for k, v in x.items()}
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: object, y: object):
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: object, y: object):
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.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 display_resolution(self, *args, **kwargs):
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
- grp.sort(key=lambda x: x.handler.__name__)
274
- match = [
275
- dependent_match(self.type_tuples[c.base_handler], [*args, *kwargs.items()])
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
- ambiguous = len([m for m in match if m]) > 1
279
- for m, c in zip(match, grp):
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):