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.
Files changed (144) hide show
  1. ovld-0.4.3/.bsync-snap-20240916114026.355340 +0 -0
  2. {ovld-0.4.1 → ovld-0.4.3}/PKG-INFO +1 -1
  3. {ovld-0.4.1 → ovld-0.4.3}/pyproject.toml +1 -1
  4. ovld-0.4.3/reddit.md +54 -0
  5. ovld-0.4.3/reddit.py +24 -0
  6. {ovld-0.4.1 → ovld-0.4.3}/src/ovld/__init__.py +8 -0
  7. {ovld-0.4.1 → ovld-0.4.3}/src/ovld/core.py +12 -3
  8. {ovld-0.4.1 → ovld-0.4.3}/src/ovld/dependent.py +16 -9
  9. {ovld-0.4.1 → ovld-0.4.3}/src/ovld/mro.py +16 -15
  10. {ovld-0.4.1 → ovld-0.4.3}/src/ovld/recode.py +7 -5
  11. {ovld-0.4.1 → ovld-0.4.3}/src/ovld/typemap.py +48 -21
  12. {ovld-0.4.1 → ovld-0.4.3}/src/ovld/types.py +26 -14
  13. ovld-0.4.3/src/ovld/version.py +1 -0
  14. ovld-0.4.3/tests/test_mro.py +112 -0
  15. {ovld-0.4.1 → ovld-0.4.3}/tests/test_ovld.py +59 -0
  16. {ovld-0.4.1 → ovld-0.4.3}/tests/test_typemap.py +1 -1
  17. {ovld-0.4.1 → ovld-0.4.3}/todo.q +4 -0
  18. {ovld-0.4.1 → ovld-0.4.3}/uv.lock +1 -1
  19. ovld-0.4.1/src/ovld/version.py +0 -1
  20. ovld-0.4.1/tests/test_mro.py +0 -51
  21. {ovld-0.4.1 → ovld-0.4.3}/.bsync-snap-20220324145902.852076 +0 -0
  22. {ovld-0.4.1 → ovld-0.4.3}/.envrc +0 -0
  23. {ovld-0.4.1 → ovld-0.4.3}/.github/workflows/python-package.yml +0 -0
  24. {ovld-0.4.1 → ovld-0.4.3}/.gitignore +0 -0
  25. {ovld-0.4.1 → ovld-0.4.3}/.python-version +0 -0
  26. {ovld-0.4.1 → ovld-0.4.3}/.readthedocs.yaml +0 -0
  27. {ovld-0.4.1 → ovld-0.4.3}/LICENSE +0 -0
  28. {ovld-0.4.1 → ovld-0.4.3}/README.md +0 -0
  29. {ovld-0.4.1 → ovld-0.4.3}/add.py +0 -0
  30. {ovld-0.4.1 → ovld-0.4.3}/anal.py +0 -0
  31. {ovld-0.4.1 → ovld-0.4.3}/bench/requirements.txt +0 -0
  32. {ovld-0.4.1 → ovld-0.4.3}/bench.py +0 -0
  33. {ovld-0.4.1 → ovld-0.4.3}/benchd.py +0 -0
  34. {ovld-0.4.1 → ovld-0.4.3}/benchmarks/__init__.py +0 -0
  35. {ovld-0.4.1 → ovld-0.4.3}/benchmarks/common.py +0 -0
  36. {ovld-0.4.1 → ovld-0.4.3}/benchmarks/test_add.py +0 -0
  37. {ovld-0.4.1 → ovld-0.4.3}/benchmarks/test_ast.py +0 -0
  38. {ovld-0.4.1 → ovld-0.4.3}/benchmarks/test_calc.py +0 -0
  39. {ovld-0.4.1 → ovld-0.4.3}/benchmarks/test_fib.py +0 -0
  40. {ovld-0.4.1 → ovld-0.4.3}/benchmarks/test_multer.py +0 -0
  41. {ovld-0.4.1 → ovld-0.4.3}/benchmarks/test_trivial.py +0 -0
  42. {ovld-0.4.1 → ovld-0.4.3}/benchmarks/test_tweaknum.py +0 -0
  43. {ovld-0.4.1 → ovld-0.4.3}/cloz.py +0 -0
  44. {ovld-0.4.1 → ovld-0.4.3}/data.py +0 -0
  45. {ovld-0.4.1 → ovld-0.4.3}/didi.py +0 -0
  46. {ovld-0.4.1 → ovld-0.4.3}/docs/compare.md +0 -0
  47. {ovld-0.4.1 → ovld-0.4.3}/docs/dependent.md +0 -0
  48. {ovld-0.4.1 → ovld-0.4.3}/docs/features.md +0 -0
  49. {ovld-0.4.1 → ovld-0.4.3}/docs/index.md +0 -0
  50. {ovld-0.4.1 → ovld-0.4.3}/docs/types.md +0 -0
  51. {ovld-0.4.1 → ovld-0.4.3}/docs/usage.md +0 -0
  52. {ovld-0.4.1 → ovld-0.4.3}/doo.py +0 -0
  53. {ovld-0.4.1 → ovld-0.4.3}/edges.py +0 -0
  54. {ovld-0.4.1 → ovld-0.4.3}/explore.py +0 -0
  55. {ovld-0.4.1 → ovld-0.4.3}/facto.py +0 -0
  56. {ovld-0.4.1 → ovld-0.4.3}/gen_comparison_table.py +0 -0
  57. {ovld-0.4.1 → ovld-0.4.3}/gentest.py +0 -0
  58. {ovld-0.4.1 → ovld-0.4.3}/hello.py +0 -0
  59. {ovld-0.4.1 → ovld-0.4.3}/hntest.py +0 -0
  60. {ovld-0.4.1 → ovld-0.4.3}/klos.py +0 -0
  61. {ovld-0.4.1 → ovld-0.4.3}/mkdocs.yml +0 -0
  62. {ovld-0.4.1 → ovld-0.4.3}/nxt.py +0 -0
  63. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.0-py3-none-any.whl +0 -0
  64. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.0.tar.gz +0 -0
  65. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.1-py3-none-any.whl +0 -0
  66. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.1.tar.gz +0 -0
  67. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.2-py3-none-any.whl +0 -0
  68. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.2.tar.gz +0 -0
  69. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.3-py3-none-any.whl +0 -0
  70. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.3.tar.gz +0 -0
  71. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.4-py3-none-any.whl +0 -0
  72. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.4.tar.gz +0 -0
  73. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.5-py3-none-any.whl +0 -0
  74. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.5.tar.gz +0 -0
  75. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.6-py3-none-any.whl +0 -0
  76. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.6.tar.gz +0 -0
  77. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.7-py3-none-any.whl +0 -0
  78. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.7.tar.gz +0 -0
  79. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.8-py3-none-any.whl +0 -0
  80. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.8.tar.gz +0 -0
  81. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.9-py3-none-any.whl +0 -0
  82. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.1.9.tar.gz +0 -0
  83. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.0-py3-none-any.whl +0 -0
  84. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.0.tar.gz +0 -0
  85. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.1-py3-none-any.whl +0 -0
  86. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.1.tar.gz +0 -0
  87. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.10-py3-none-any.whl +0 -0
  88. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.10.tar.gz +0 -0
  89. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.11-py3-none-any.whl +0 -0
  90. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.11.tar.gz +0 -0
  91. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.2-py3-none-any.whl +0 -0
  92. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.2.tar.gz +0 -0
  93. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.3-py3-none-any.whl +0 -0
  94. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.3.tar.gz +0 -0
  95. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.4-py3-none-any.whl +0 -0
  96. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.4.tar.gz +0 -0
  97. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.5-py3-none-any.whl +0 -0
  98. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.5.tar.gz +0 -0
  99. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.6-py3-none-any.whl +0 -0
  100. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.6.tar.gz +0 -0
  101. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.7-py3-none-any.whl +0 -0
  102. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.7.tar.gz +0 -0
  103. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.8-py3-none-any.whl +0 -0
  104. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.8.tar.gz +0 -0
  105. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.9-py3-none-any.whl +0 -0
  106. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.2.9.tar.gz +0 -0
  107. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.0-py3-none-any.whl +0 -0
  108. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.0.tar.gz +0 -0
  109. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.1-py3-none-any.whl +0 -0
  110. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.1.tar.gz +0 -0
  111. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.2-py3-none-any.whl +0 -0
  112. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.2.tar.gz +0 -0
  113. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.3-py3-none-any.whl +0 -0
  114. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.3.tar.gz +0 -0
  115. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.4-py3-none-any.whl +0 -0
  116. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.4.tar.gz +0 -0
  117. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.5-py3-none-any.whl +0 -0
  118. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.5.tar.gz +0 -0
  119. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.8-py3-none-any.whl +0 -0
  120. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.8.tar.gz +0 -0
  121. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.9-py3-none-any.whl +0 -0
  122. {ovld-0.4.1 → ovld-0.4.3}/old-dist/ovld-0.3.9.tar.gz +0 -0
  123. {ovld-0.4.1 → ovld-0.4.3}/one_two_three.py +0 -0
  124. {ovld-0.4.1 → ovld-0.4.3}/outoftheway.toml +0 -0
  125. {ovld-0.4.1 → ovld-0.4.3}/src/ovld/utils.py +0 -0
  126. {ovld-0.4.1 → ovld-0.4.3}/stuff.md +0 -0
  127. {ovld-0.4.1 → ovld-0.4.3}/tb.py +0 -0
  128. {ovld-0.4.1 → ovld-0.4.3}/tensor.py +0 -0
  129. {ovld-0.4.1 → ovld-0.4.3}/tests/__init__.py +0 -0
  130. {ovld-0.4.1 → ovld-0.4.3}/tests/modules/gingerbread.py +0 -0
  131. {ovld-0.4.1 → ovld-0.4.3}/tests/test_dependent.py +0 -0
  132. {ovld-0.4.1 → ovld-0.4.3}/tests/test_examples.py +0 -0
  133. {ovld-0.4.1 → ovld-0.4.3}/tests/test_global.py +0 -0
  134. {ovld-0.4.1 → ovld-0.4.3}/tests/test_ovld/test_display.txt +0 -0
  135. {ovld-0.4.1 → ovld-0.4.3}/tests/test_ovld/test_doc.txt +0 -0
  136. {ovld-0.4.1 → ovld-0.4.3}/tests/test_ovld/test_doc2.txt +0 -0
  137. {ovld-0.4.1 → ovld-0.4.3}/tests/test_ovld/test_method_doc.txt +0 -0
  138. {ovld-0.4.1 → ovld-0.4.3}/tests/test_types.py +0 -0
  139. {ovld-0.4.1 → ovld-0.4.3}/tests/test_utils.py +0 -0
  140. {ovld-0.4.1 → ovld-0.4.3}/toot.py +0 -0
  141. {ovld-0.4.1 → ovld-0.4.3}/typo.py +0 -0
  142. {ovld-0.4.1 → ovld-0.4.3}/world.yaml +0 -0
  143. {ovld-0.4.1 → ovld-0.4.3}/x.py +0 -0
  144. {ovld-0.4.1 → ovld-0.4.3}/zaggo +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ovld
3
- Version: 0.4.1
3
+ Version: 0.4.3
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/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ovld"
3
- version = "0.4.1"
3
+ version = "0.4.3"
4
4
  description = "Overloading Python functions"
5
5
  authors = [
6
6
  { name = "Olivier Breuleux", email = "breuleux@gmail.com" }
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 p, prio, spc in possibilities:
397
- hlp += f"* {p.__name__} (priority: {prio}, specificity: {list(spc)})\n"
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
- self._defns[sig] = fn
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 __typeorder__(self, other):
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 TypeRelationship(Order.LESS, matches=False)
76
+ return Order.LESS
78
77
  elif other < self: # pragma: no cover
79
- return TypeRelationship(Order.MORE, matches=False)
78
+ return Order.MORE
80
79
  else:
81
- return TypeRelationship(Order.NONE, matches=False)
80
+ return Order.NONE
82
81
  else: # pragma: no cover
83
- return TypeRelationship(order, matches=False)
84
- elif (matches := subclasscheck(other, self.bound)) or subclasscheck(
82
+ return order
83
+ elif subclasscheck(other, self.bound) or subclasscheck(
85
84
  self.bound, other
86
85
  ):
87
- return TypeRelationship(Order.LESS, matches=matches)
86
+ return Order.LESS
88
87
  else:
89
- return TypeRelationship(Order.NONE, matches=False)
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
- matches: bool = None
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, "__typeorder__")
56
- and (result := t1.__typeorder__(t2)) is not NotImplemented
53
+ hasattr(t1, "__type_order__")
54
+ and (result := t1.__type_order__(t2)) is not NotImplemented
57
55
  ):
58
- return result.order
56
+ return result
59
57
  elif (
60
- hasattr(t2, "__typeorder__")
61
- and (result := t2.__typeorder__(t1)) is not NotImplemented
58
+ hasattr(t2, "__type_order__")
59
+ and (result := t2.__type_order__(t1)) is not NotImplemented
62
60
  ):
63
- return result.order.opposite()
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
- t1 = getattr(t1, "__proxy_for__", t1)
139
- t2 = getattr(t2, "__proxy_for__", t2)
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(t2, "__typeorder__")
143
- and (result := t2.__typeorder__(t1)) is not NotImplemented
143
+ hasattr(t1, "__is_subtype__")
144
+ and (result := t1.__is_subtype__(t2)) is not NotImplemented
144
145
  ):
145
- return result.matches
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"__TMP{key}", ctx=ast.Store()),
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="__TMPM", ctx=ast.Load()),
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="__TMPM", ctx=ast.Store()),
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"__TMP{i}", ctx=ast.Load())
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"__TMP{kw.arg}", ctx=ast.Load()),
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
- (c, self.priorities.get(c, 0), tuple(specificities[c]))
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
- candidates.sort(key=lambda cspc: (cspc[1], sum(cspc[2])), reverse=True)
195
+
196
+ candidates.sort(key=Candidate.sort_key, reverse=True)
168
197
 
169
198
  self.all[obj_t_tup] = {
170
- getattr(c[0], "__code__", None) for c in candidates
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, p1, spc1 = candidates[0]
183
- for c2, p2, spc2 in candidates[1:]:
184
- if p1 > p2 or (
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((c2, p2, spc2))
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[0].__name__)
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 handler, _, _ in grp
291
+ for c in grp
266
292
  ]
267
293
  ambiguous = len([m for m in match if m]) > 1
268
- for m, (handler, prio, spec) in zip(match, grp):
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, spec))
286
- lvl = f"[{prio}:{spec}]"
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 = [fn for (fn, _, _) in group]
324
- dependent = any(self.dependent[fn] for (fn, _, _) in group)
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 __typeorder__(cls, other):
65
- order = cls.order(other)
66
- if isinstance(order, bool):
67
- return NotImplemented
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 order
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
- result = cls.order(sub)
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
- matches=cls is base_cls,
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, matches=matches)
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, matches=matches)
201
+ return TypeRelationship(Order.LESS, supertype=matches)
190
202
  else:
191
- return TypeRelationship(Order.MORE, matches=matches)
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