| | from .utils import _toposort, groupby |
| | from .variadic import isvariadic |
| |
|
| | __all__ = ["AmbiguityWarning", "supercedes", "consistent", "ambiguous", "ambiguities", "super_signature", |
| | "edge", "ordering"] |
| |
|
| | class AmbiguityWarning(Warning): |
| | pass |
| |
|
| |
|
| | def supercedes(a, b): |
| | """ A is consistent and strictly more specific than B """ |
| | if len(a) < len(b): |
| | |
| | return not a and len(b) == 1 and isvariadic(b[-1]) |
| | elif len(a) == len(b): |
| | return all(map(issubclass, a, b)) |
| | else: |
| | |
| | p1 = 0 |
| | p2 = 0 |
| | while p1 < len(a) and p2 < len(b): |
| | cur_a = a[p1] |
| | cur_b = b[p2] |
| | if not (isvariadic(cur_a) or isvariadic(cur_b)): |
| | if not issubclass(cur_a, cur_b): |
| | return False |
| | p1 += 1 |
| | p2 += 1 |
| | elif isvariadic(cur_a): |
| | assert p1 == len(a) - 1 |
| | return p2 == len(b) - 1 and issubclass(cur_a, cur_b) |
| | elif isvariadic(cur_b): |
| | assert p2 == len(b) - 1 |
| | if not issubclass(cur_a, cur_b): |
| | return False |
| | p1 += 1 |
| | return p2 == len(b) - 1 and p1 == len(a) |
| |
|
| |
|
| | def consistent(a, b): |
| | """ It is possible for an argument list to satisfy both A and B """ |
| |
|
| | |
| | if not a: |
| | return not b or isvariadic(b[0]) |
| | if not b: |
| | return not a or isvariadic(a[0]) |
| |
|
| | |
| | if len(a) == len(b): |
| | return all(issubclass(aa, bb) or issubclass(bb, aa) |
| | for aa, bb in zip(a, b)) |
| | else: |
| | p1 = 0 |
| | p2 = 0 |
| | while p1 < len(a) and p2 < len(b): |
| | cur_a = a[p1] |
| | cur_b = b[p2] |
| | if not issubclass(cur_b, cur_a) and not issubclass(cur_a, cur_b): |
| | return False |
| | if not (isvariadic(cur_a) or isvariadic(cur_b)): |
| | p1 += 1 |
| | p2 += 1 |
| | elif isvariadic(cur_a): |
| | p2 += 1 |
| | elif isvariadic(cur_b): |
| | p1 += 1 |
| | |
| | |
| | return (isvariadic(cur_a) and p2 == len(b) or |
| | isvariadic(cur_b) and p1 == len(a)) |
| |
|
| |
|
| | def ambiguous(a, b): |
| | """ A is consistent with B but neither is strictly more specific """ |
| | return consistent(a, b) and not (supercedes(a, b) or supercedes(b, a)) |
| |
|
| |
|
| | def ambiguities(signatures): |
| | """ All signature pairs such that A is ambiguous with B """ |
| | signatures = list(map(tuple, signatures)) |
| | return set((a, b) for a in signatures for b in signatures |
| | if hash(a) < hash(b) |
| | and ambiguous(a, b) |
| | and not any(supercedes(c, a) and supercedes(c, b) |
| | for c in signatures)) |
| |
|
| |
|
| | def super_signature(signatures): |
| | """ A signature that would break ambiguities """ |
| | n = len(signatures[0]) |
| | assert all(len(s) == n for s in signatures) |
| |
|
| | return [max([type.mro(sig[i]) for sig in signatures], key=len)[0] |
| | for i in range(n)] |
| |
|
| |
|
| | def edge(a, b, tie_breaker=hash): |
| | """ A should be checked before B |
| | Tie broken by tie_breaker, defaults to ``hash`` |
| | """ |
| | |
| | |
| | return supercedes(a, b) and (not supercedes(b, a) or tie_breaker(a) > tie_breaker(b)) |
| |
|
| |
|
| | def ordering(signatures): |
| | """ A sane ordering of signatures to check, first to last |
| | Topoological sort of edges as given by ``edge`` and ``supercedes`` |
| | """ |
| | signatures = list(map(tuple, signatures)) |
| | edges = [(a, b) for a in signatures for b in signatures if edge(a, b)] |
| | edges = groupby(lambda x: x[0], edges) |
| | for s in signatures: |
| | if s not in edges: |
| | edges[s] = [] |
| | edges = dict((k, [b for a, b in v]) for k, v in edges.items()) |
| | return _toposort(edges) |
| |
|