python-constricter 0.4.1__cp312-cp312-win32.whl
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.
- constricter/__init__.py +55 -0
- constricter/__main__.py +26 -0
- constricter/_version.py +4 -0
- constricter/cli/__init__.py +2 -0
- constricter/cli/baseline.py +107 -0
- constricter/cli/collecting.py +54 -0
- constricter/cli/command.py +547 -0
- constricter/cli/config.py +322 -0
- constricter/cli/explain.py +104 -0
- constricter/cli/lsp/__init__.py +2 -0
- constricter/cli/lsp/connection.py +475 -0
- constricter/cli/lsp/edits.py +188 -0
- constricter/cli/lsp/guard.py +133 -0
- constricter/cli/lsp/hints.py +391 -0
- constricter/cli/lsp/protocol.py +192 -0
- constricter/cli/lsp/remembered.py +81 -0
- constricter/cli/lsp/stored.py +200 -0
- constricter/cli/options.py +799 -0
- constricter/cli/paths.py +151 -0
- constricter/cli/reading.py +36 -0
- constricter/cli/report.py +397 -0
- constricter/cli/rounds.py +328 -0
- constricter/cli/runs.py +90 -0
- constricter/cli/schedule.py +463 -0
- constricter/cli/traced.py +401 -0
- constricter/cli/workers.py +286 -0
- constricter/empty.py +66 -0
- constricter/fix/__init__.cp312-win32.pyd +0 -0
- constricter/fix/__init__.py +2 -0
- constricter/fix/core/__init__.cp312-win32.pyd +0 -0
- constricter/fix/core/__init__.py +2 -0
- constricter/fix/core/asked.cp312-win32.pyd +0 -0
- constricter/fix/core/asked.py +56 -0
- constricter/fix/core/fixes.cp312-win32.pyd +0 -0
- constricter/fix/core/fixes.py +317 -0
- constricter/fix/core/imports.cp312-win32.pyd +0 -0
- constricter/fix/core/imports.py +580 -0
- constricter/fix/core/inherited.cp312-win32.pyd +0 -0
- constricter/fix/core/inherited.py +311 -0
- constricter/fix/core/known.cp312-win32.pyd +0 -0
- constricter/fix/core/known.py +730 -0
- constricter/fix/core/signatures.cp312-win32.pyd +0 -0
- constricter/fix/core/signatures.py +132 -0
- constricter/fix/index/__init__.cp312-win32.pyd +0 -0
- constricter/fix/index/__init__.py +2 -0
- constricter/fix/index/awaits.cp312-win32.pyd +0 -0
- constricter/fix/index/awaits.py +90 -0
- constricter/fix/index/beyond.cp312-win32.pyd +0 -0
- constricter/fix/index/beyond.py +149 -0
- constricter/fix/index/callers.cp312-win32.pyd +0 -0
- constricter/fix/index/callers.py +155 -0
- constricter/fix/index/crossed.cp312-win32.pyd +0 -0
- constricter/fix/index/crossed.py +327 -0
- constricter/fix/index/decorated.cp312-win32.pyd +0 -0
- constricter/fix/index/decorated.py +140 -0
- constricter/fix/index/fixtures.cp312-win32.pyd +0 -0
- constricter/fix/index/fixtures.py +176 -0
- constricter/fix/index/linked.cp312-win32.pyd +0 -0
- constricter/fix/index/linked.py +142 -0
- constricter/fix/index/loose.cp312-win32.pyd +0 -0
- constricter/fix/index/loose.py +103 -0
- constricter/fix/index/managed.cp312-win32.pyd +0 -0
- constricter/fix/index/managed.py +63 -0
- constricter/fix/index/modules.cp312-win32.pyd +0 -0
- constricter/fix/index/modules.py +741 -0
- constricter/fix/index/offers.cp312-win32.pyd +0 -0
- constricter/fix/index/offers.py +252 -0
- constricter/fix/index/order.cp312-win32.pyd +0 -0
- constricter/fix/index/order.py +155 -0
- constricter/fix/index/origins.cp312-win32.pyd +0 -0
- constricter/fix/index/origins.py +238 -0
- constricter/fix/index/own_methods.cp312-win32.pyd +0 -0
- constricter/fix/index/own_methods.py +91 -0
- constricter/fix/index/own_overloads.cp312-win32.pyd +0 -0
- constricter/fix/index/own_overloads.py +70 -0
- constricter/fix/index/own_types.cp312-win32.pyd +0 -0
- constricter/fix/index/own_types.py +330 -0
- constricter/fix/index/params.cp312-win32.pyd +0 -0
- constricter/fix/index/params.py +81 -0
- constricter/fix/index/plain.cp312-win32.pyd +0 -0
- constricter/fix/index/plain.py +221 -0
- constricter/fix/index/project.cp312-win32.pyd +0 -0
- constricter/fix/index/project.py +797 -0
- constricter/fix/index/sides.cp312-win32.pyd +0 -0
- constricter/fix/index/sides.py +160 -0
- constricter/fix/index/strings.cp312-win32.pyd +0 -0
- constricter/fix/index/strings.py +46 -0
- constricter/fix/index/stubs/__init__.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/__init__.py +2 -0
- constricter/fix/index/stubs/atoms.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/atoms.py +425 -0
- constricter/fix/index/stubs/chained.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/chained.py +69 -0
- constricter/fix/index/stubs/classnames.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/classnames.py +124 -0
- constricter/fix/index/stubs/declared.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/declared.py +508 -0
- constricter/fix/index/stubs/installed.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/installed.py +425 -0
- constricter/fix/index/stubs/names.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/names.py +200 -0
- constricter/fix/index/stubs/read.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/read.py +70 -0
- constricter/fix/index/stubs/signatures.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/signatures.py +656 -0
- constricter/fix/index/stubs/stubbed.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/stubbed.py +208 -0
- constricter/fix/index/taken.cp312-win32.pyd +0 -0
- constricter/fix/index/taken.py +45 -0
- constricter/fix/index/tuples.cp312-win32.pyd +0 -0
- constricter/fix/index/tuples.py +112 -0
- constricter/fix/libraries/__init__.cp312-win32.pyd +0 -0
- constricter/fix/libraries/__init__.py +2 -0
- constricter/fix/libraries/library.cp312-win32.pyd +0 -0
- constricter/fix/libraries/library.py +420 -0
- constricter/fix/libraries/opened.cp312-win32.pyd +0 -0
- constricter/fix/libraries/opened.py +147 -0
- constricter/fix/libraries/overloads.cp312-win32.pyd +0 -0
- constricter/fix/libraries/overloads.py +960 -0
- constricter/fix/libraries/receivers.cp312-win32.pyd +0 -0
- constricter/fix/libraries/receivers.py +274 -0
- constricter/fix/libraries/stdlib.cp312-win32.pyd +0 -0
- constricter/fix/libraries/stdlib.py +752 -0
- constricter/fix/libraries/walks.cp312-win32.pyd +0 -0
- constricter/fix/libraries/walks.py +49 -0
- constricter/fix/tables/aliases.json +147 -0
- constricter/fix/tables/attributes.json +700 -0
- constricter/fix/tables/awaited.json +69 -0
- constricter/fix/tables/awaited_overloads.json +14 -0
- constricter/fix/tables/bases.json +888 -0
- constricter/fix/tables/classes.json +1941 -0
- constricter/fix/tables/elements.json +156 -0
- constricter/fix/tables/functions.json +58 -0
- constricter/fix/tables/generic_attributes.json +254 -0
- constricter/fix/tables/method_overloads.json +461 -0
- constricter/fix/tables/method_signatures.json +954 -0
- constricter/fix/tables/methods.json +954 -0
- constricter/fix/tables/overloads.json +540 -0
- constricter/fix/tables/platform.json +37 -0
- constricter/fix/tables/returns.json +1362 -0
- constricter/fix/tables/scalar_members.json +16 -0
- constricter/fix/tables/scalars.json +2707 -0
- constricter/fix/tables/source.json +28 -0
- constricter/fix/tables/subscriptable.json +77 -0
- constricter/fix/tables/type_parameters.json +178 -0
- constricter/fix/tables/variables.json +2962 -0
- constricter/fix/values/__init__.cp312-win32.pyd +0 -0
- constricter/fix/values/__init__.py +2 -0
- constricter/fix/values/aliased.cp312-win32.pyd +0 -0
- constricter/fix/values/aliased.py +195 -0
- constricter/fix/values/bodies.cp312-win32.pyd +0 -0
- constricter/fix/values/bodies.py +76 -0
- constricter/fix/values/callables.cp312-win32.pyd +0 -0
- constricter/fix/values/callables.py +323 -0
- constricter/fix/values/called.cp312-win32.pyd +0 -0
- constricter/fix/values/called.py +194 -0
- constricter/fix/values/classvars.cp312-win32.pyd +0 -0
- constricter/fix/values/classvars.py +629 -0
- constricter/fix/values/decided.cp312-win32.pyd +0 -0
- constricter/fix/values/decided.py +429 -0
- constricter/fix/values/displays.cp312-win32.pyd +0 -0
- constricter/fix/values/displays.py +101 -0
- constricter/fix/values/doubts.cp312-win32.pyd +0 -0
- constricter/fix/values/doubts.py +677 -0
- constricter/fix/values/entered.cp312-win32.pyd +0 -0
- constricter/fix/values/entered.py +332 -0
- constricter/fix/values/fills.cp312-win32.pyd +0 -0
- constricter/fix/values/fills.py +446 -0
- constricter/fix/values/guesses.cp312-win32.pyd +0 -0
- constricter/fix/values/guesses.py +376 -0
- constricter/fix/values/hinted.cp312-win32.pyd +0 -0
- constricter/fix/values/hinted.py +811 -0
- constricter/fix/values/inference.cp312-win32.pyd +0 -0
- constricter/fix/values/inference.py +698 -0
- constricter/fix/values/loops.cp312-win32.pyd +0 -0
- constricter/fix/values/loops.py +335 -0
- constricter/fix/values/members.cp312-win32.pyd +0 -0
- constricter/fix/values/members.py +578 -0
- constricter/fix/values/narrowed.cp312-win32.pyd +0 -0
- constricter/fix/values/narrowed.py +220 -0
- constricter/fix/values/operated.cp312-win32.pyd +0 -0
- constricter/fix/values/operated.py +202 -0
- constricter/fix/values/ordered.cp312-win32.pyd +0 -0
- constricter/fix/values/ordered.py +74 -0
- constricter/fix/values/returned.cp312-win32.pyd +0 -0
- constricter/fix/values/returned.py +479 -0
- constricter/fix/values/returns.cp312-win32.pyd +0 -0
- constricter/fix/values/returns.py +237 -0
- constricter/fix/values/shapes.cp312-win32.pyd +0 -0
- constricter/fix/values/shapes.py +654 -0
- constricter/fix/values/stored.cp312-win32.pyd +0 -0
- constricter/fix/values/stored.py +564 -0
- constricter/fix/values/targets.cp312-win32.pyd +0 -0
- constricter/fix/values/targets.py +419 -0
- constricter/fix/values/unchecked.cp312-win32.pyd +0 -0
- constricter/fix/values/unchecked.py +278 -0
- constricter/jsonc.py +50 -0
- constricter/noqa.py +47 -0
- constricter/notebook.py +115 -0
- constricter/offences.py +436 -0
- constricter/overrides.py +24 -0
- constricter/plugins/__init__.py +2 -0
- constricter/plugins/flake8.py +133 -0
- constricter/plugins/pylint.py +186 -0
- constricter/py.typed +0 -0
- constricter/recording.py +660 -0
- constricter/rules/__init__.cp312-win32.pyd +0 -0
- constricter/rules/__init__.py +2 -0
- constricter/rules/annotations.cp312-win32.pyd +0 -0
- constricter/rules/annotations.py +296 -0
- constricter/rules/binding.cp312-win32.pyd +0 -0
- constricter/rules/binding.py +652 -0
- constricter/rules/calls.cp312-win32.pyd +0 -0
- constricter/rules/calls.py +460 -0
- constricter/rules/checker.cp312-win32.pyd +0 -0
- constricter/rules/checker.py +536 -0
- constricter/rules/declarations.cp312-win32.pyd +0 -0
- constricter/rules/declarations.py +730 -0
- constricter/rules/decorators.cp312-win32.pyd +0 -0
- constricter/rules/decorators.py +351 -0
- constricter/rules/flow.cp312-win32.pyd +0 -0
- constricter/rules/flow.py +502 -0
- constricter/rules/keys.cp312-win32.pyd +0 -0
- constricter/rules/keys.py +79 -0
- constricter/rules/late.cp312-win32.pyd +0 -0
- constricter/rules/late.py +969 -0
- constricter/rules/narrowing.cp312-win32.pyd +0 -0
- constricter/rules/narrowing.py +98 -0
- constricter/rules/parsed.cp312-win32.pyd +0 -0
- constricter/rules/parsed.py +136 -0
- constricter/rules/quoted.cp312-win32.pyd +0 -0
- constricter/rules/quoted.py +141 -0
- constricter/rules/rebinding.cp312-win32.pyd +0 -0
- constricter/rules/rebinding.py +169 -0
- constricter/rules/recorded.cp312-win32.pyd +0 -0
- constricter/rules/recorded.py +168 -0
- constricter/rules/redundant.cp312-win32.pyd +0 -0
- constricter/rules/redundant.py +160 -0
- constricter/rules/scope.cp312-win32.pyd +0 -0
- constricter/rules/scope.py +802 -0
- constricter/rules/scoped.cp312-win32.pyd +0 -0
- constricter/rules/scoped.py +232 -0
- constricter/rules/scoping.cp312-win32.pyd +0 -0
- constricter/rules/scoping.py +346 -0
- constricter/rules/settings.cp312-win32.pyd +0 -0
- constricter/rules/settings.py +189 -0
- constricter/rules/syntax.cp312-win32.pyd +0 -0
- constricter/rules/syntax.py +472 -0
- constricter/rules/tables.cp312-win32.pyd +0 -0
- constricter/rules/tables.py +169 -0
- constricter/rules/walked.cp312-win32.pyd +0 -0
- constricter/rules/walked.py +127 -0
- constricter/rules/widened.cp312-win32.pyd +0 -0
- constricter/rules/widened.py +322 -0
- constricter/trace.py +374 -0
- e45ce06b2fdd5c43f782__mypyc.cp312-win32.pyd +0 -0
- python_constricter-0.4.1.dist-info/METADATA +400 -0
- python_constricter-0.4.1.dist-info/RECORD +261 -0
- python_constricter-0.4.1.dist-info/WHEEL +4 -0
- python_constricter-0.4.1.dist-info/entry_points.txt +5 -0
- python_constricter-0.4.1.dist-info/licenses/LICENSE.md +21 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Which class an instance's member comes from: its own, or the base that defines it.
|
|
3
|
+
|
|
4
|
+
A class the module defines takes a member its body doesn't bind from its bases, in method
|
|
5
|
+
resolution order, as far as the module sees: its own classes, each defined once and not generic,
|
|
6
|
+
then a class another checked file or the standard library's tables define, whose members are
|
|
7
|
+
known whole. The order ends there, or before the first base out of sight (an installed package's,
|
|
8
|
+
subscripted, computed), which may define anything.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import ast
|
|
12
|
+
import builtins
|
|
13
|
+
from collections import Counter
|
|
14
|
+
from collections.abc import Iterator, Mapping, Sequence
|
|
15
|
+
from types import MappingProxyType
|
|
16
|
+
from typing import Final, NamedTuple, TypeAlias, cast
|
|
17
|
+
|
|
18
|
+
from constricter.fix.core.signatures import AWAIT
|
|
19
|
+
from constricter.rules.annotations import node_name
|
|
20
|
+
from constricter.rules.declarations import generic_classes
|
|
21
|
+
from constricter.rules.syntax import FUNCTION_DEFS, child_statements, expressions
|
|
22
|
+
from constricter.rules.walked import classes, walk
|
|
23
|
+
|
|
24
|
+
# Bases that give an instance no member a method call could reach.
|
|
25
|
+
_EMPTY: Final = frozenset({"object", "Generic", "Protocol", "ABC"})
|
|
26
|
+
# The standard-library class a line of bases ends at (`""`: at none, the line known whole), and the
|
|
27
|
+
# names the classes before it bind. Several classes, where two lines each end at one: their paths,
|
|
28
|
+
# in order, `SEVERAL` between them.
|
|
29
|
+
Beyond: TypeAlias = tuple[str, frozenset[str]]
|
|
30
|
+
SEVERAL: Final = ","
|
|
31
|
+
# A standard-library class held whole: its members' names, and its own and its ancestors' paths.
|
|
32
|
+
Library: TypeAlias = tuple[frozenset[str], frozenset[str]]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Lineage(NamedTuple):
|
|
36
|
+
"""The module's classes' ancestry, as far as it sees.
|
|
37
|
+
|
|
38
|
+
`order`: each class's method resolution order after itself, up to the first base out of sight,
|
|
39
|
+
or a class another checked file or the standard library defines (as the module spells it), which
|
|
40
|
+
ends it; `bound`: the names each class's body binds; `selfish`: its methods declared to return a
|
|
41
|
+
bare `Self` (see `self_returns`), which an inheriting class's instance gives as its own class.
|
|
42
|
+
`beyond`: for a base another checked file defines, the standard-library class its own bases end
|
|
43
|
+
at, and the names bound on the way there (see `constricter.fix.index.beyond`); one whose bases
|
|
44
|
+
end at none (a mixin) doesn't end the order, which goes on past it for what it doesn't bind.
|
|
45
|
+
`library`: the standard-library classes held whole that an order goes on past, or that a line
|
|
46
|
+
of bases ends at beside another (see `SEVERAL`), each with its members' names.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
order: Mapping[str, tuple[str, ...]] = MappingProxyType({})
|
|
50
|
+
bound: Mapping[str, frozenset[str]] = MappingProxyType({})
|
|
51
|
+
selfish: Mapping[str, frozenset[str]] = MappingProxyType({})
|
|
52
|
+
beyond: Mapping[str, Beyond] = MappingProxyType({})
|
|
53
|
+
library: Mapping[str, frozenset[str]] = MappingProxyType({})
|
|
54
|
+
|
|
55
|
+
def definer(self, receiver: str, name: str) -> str | None:
|
|
56
|
+
"""Find the class whose `name` an instance of `receiver` has.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
`receiver` itself if it binds `name`, or isn't one of the module's classes; else the first
|
|
60
|
+
class of its order that does, or that another file defines; `None` if none in sight does.
|
|
61
|
+
For another file's class whose bases end at a standard-library class, that class, where
|
|
62
|
+
nothing on the way binds `name` (see `beyond`).
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
found: str | None = (
|
|
66
|
+
receiver
|
|
67
|
+
if receiver not in self.bound or name in self.bound[receiver]
|
|
68
|
+
else next((base for base in self.order[receiver] if self._binds(base, name)), None)
|
|
69
|
+
)
|
|
70
|
+
library: Beyond | None = self.beyond.get(found or "")
|
|
71
|
+
if library is None or name in library[1]:
|
|
72
|
+
return found
|
|
73
|
+
ends: list[str] = library[0].split(SEVERAL)
|
|
74
|
+
# Of several library classes, the first that has the member: the others' is behind it.
|
|
75
|
+
return (
|
|
76
|
+
next((end for end in ends if len(ends) == 1 or name in self.library.get(end, ())), None) or None
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def _binds(self, base: str, name: str) -> bool:
|
|
80
|
+
"""Check whether `base`, of a class's order, is where the search for `name` ends.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Whether it binds it, or may: another file's class, but one known whole that doesn't.
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
end: Beyond | None = self.beyond.get(base)
|
|
87
|
+
if end is not None and not end[0]:
|
|
88
|
+
return name in end[1]
|
|
89
|
+
if base in self.library:
|
|
90
|
+
return name in self.library[base]
|
|
91
|
+
return base not in self.bound or name in self.bound[base]
|
|
92
|
+
|
|
93
|
+
def selfish_of(self, owner: str) -> frozenset[str]:
|
|
94
|
+
"""Name the methods an instance of `owner` has that are declared to return a bare `Self`.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Its own, and those it takes from its bases in sight.
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
return self.selfish.get(owner, frozenset()).union(
|
|
101
|
+
name
|
|
102
|
+
for base in self.order.get(owner, ())
|
|
103
|
+
for name in self.selfish.get(base, ())
|
|
104
|
+
if self.definer(owner, name) == base
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def flattened(self, methods: Mapping[str, Mapping[str, str]]) -> dict[str, dict[str, str]]:
|
|
108
|
+
"""Add to each class's `methods` (their return types, by name) those it takes from its bases.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Each class's own and inherited methods; one declared to return `Self` gives the class itself.
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
found: dict[str, dict[str, str]] = {owner: dict(own) for owner, own in methods.items()}
|
|
115
|
+
owner: str
|
|
116
|
+
bases: tuple[str, ...]
|
|
117
|
+
for owner, bases in self.order.items():
|
|
118
|
+
hidden: set[str] = set(self.bound[owner])
|
|
119
|
+
base: str
|
|
120
|
+
for base in bases:
|
|
121
|
+
name: str
|
|
122
|
+
annotation: str
|
|
123
|
+
for name, annotation in methods.get(base, {}).items():
|
|
124
|
+
if name.removeprefix(AWAIT) not in hidden:
|
|
125
|
+
selfish: bool = name in self.selfish.get(base, ())
|
|
126
|
+
found.setdefault(owner, {})[name] = owner if selfish else annotation
|
|
127
|
+
hidden |= self.bound.get(base, frozenset()) | self.beyond.get(base, ("", frozenset()))[1]
|
|
128
|
+
hidden |= self.library.get(base, frozenset())
|
|
129
|
+
return found
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def lineage(
|
|
133
|
+
tree: ast.Module,
|
|
134
|
+
selfish: Mapping[str, frozenset[str]],
|
|
135
|
+
imported: frozenset[str],
|
|
136
|
+
beyond: Mapping[str, Beyond] | None = None,
|
|
137
|
+
library: Mapping[str, Library] | None = None,
|
|
138
|
+
) -> Lineage:
|
|
139
|
+
"""Read the ancestry of the classes the module defines once each.
|
|
140
|
+
|
|
141
|
+
`selfish`: its `self_returns`; `imported`: the classes other checked files and the standard
|
|
142
|
+
library define, as it spells them; `beyond`: see `Lineage`; `library`: the standard-library
|
|
143
|
+
classes held whole among them (see `Library`), and those `beyond` ends at beside another. An
|
|
144
|
+
order goes on past one of those to the next base, but one that shares an ancestor with it,
|
|
145
|
+
whose place between them the tables don't say.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
It (see `Lineage`).
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
nodes: Sequence[ast.ClassDef] = classes(tree)
|
|
152
|
+
counts: Counter[str] = Counter(node.name for node in nodes)
|
|
153
|
+
generic: frozenset[str] = generic_classes(tree)
|
|
154
|
+
# Each class's bases as written: one out of sight is no key here.
|
|
155
|
+
parents: dict[str, list[str]] = {
|
|
156
|
+
node.name: [ast.unparse(base) for base in node.bases if node_name(base) not in _EMPTY]
|
|
157
|
+
for node in nodes
|
|
158
|
+
if counts[node.name] == 1
|
|
159
|
+
}
|
|
160
|
+
# A class of the module's is seen through; so is another file's known whole (see `Lineage.beyond`).
|
|
161
|
+
seen: frozenset[str] = (frozenset(parents) - (generic - _given(nodes, imported))).union(
|
|
162
|
+
base for base, end in (beyond or {}).items() if not end[0]
|
|
163
|
+
)
|
|
164
|
+
order: dict[str, tuple[str, ...]] = {}
|
|
165
|
+
held: Mapping[str, Library] = library or {}
|
|
166
|
+
passed: set[str] = set() # the library classes an order goes on past
|
|
167
|
+
name: str
|
|
168
|
+
for name in parents:
|
|
169
|
+
after: list[str] = (_order(name, parents, frozenset()) or [name])[1:]
|
|
170
|
+
order[name] = tuple(after[: _sighted(after, (seen, imported), held, passed)])
|
|
171
|
+
return Lineage(
|
|
172
|
+
order,
|
|
173
|
+
{node.name: frozenset(_bound(node.body)) for node in nodes if node.name in parents},
|
|
174
|
+
selfish,
|
|
175
|
+
MappingProxyType({}) if beyond is None else beyond,
|
|
176
|
+
{base: held[base][0] for base in passed | several(beyond or {}) if base in held},
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _sighted(
|
|
181
|
+
after: Sequence[str],
|
|
182
|
+
known: tuple[frozenset[str], frozenset[str]],
|
|
183
|
+
held: Mapping[str, Library],
|
|
184
|
+
passed: set[str],
|
|
185
|
+
) -> int:
|
|
186
|
+
"""Count how many of a class's ancestors (`after`, in order) are in sight.
|
|
187
|
+
|
|
188
|
+
`known`: the classes seen through, and those another file or the standard library defines.
|
|
189
|
+
One of those is the last in sight, what it inherits its own to say; but a library class
|
|
190
|
+
`held` whole, which the next may follow: each one gone past is added to `passed`.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
How many.
|
|
194
|
+
|
|
195
|
+
"""
|
|
196
|
+
lines: frozenset[str] = frozenset() # the library classes so far, and their ancestors
|
|
197
|
+
last: int = 0
|
|
198
|
+
base: str
|
|
199
|
+
for base in after:
|
|
200
|
+
if base in known[0]:
|
|
201
|
+
last += 1
|
|
202
|
+
continue
|
|
203
|
+
last += base in known[1] and not held.get(base, (None, frozenset[str]()))[1] & lines
|
|
204
|
+
if base not in held or held[base][1] & lines or base == after[-1]:
|
|
205
|
+
break
|
|
206
|
+
lines |= held[base][1]
|
|
207
|
+
passed.add(base)
|
|
208
|
+
return last
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def several(beyond: Mapping[str, Beyond]) -> frozenset[str]:
|
|
212
|
+
"""Name the library classes lines of bases end at beside another (see `SEVERAL`).
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Their paths.
|
|
216
|
+
|
|
217
|
+
"""
|
|
218
|
+
return frozenset(end for found, _ in beyond.values() if SEVERAL in found for end in found.split(SEVERAL))
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _given(nodes: Sequence[ast.ClassDef], imported: frozenset[str]) -> frozenset[str]:
|
|
222
|
+
"""Name the classes that are generic only by a library base given its arguments: not generic at all.
|
|
223
|
+
|
|
224
|
+
`class Rows(collections.deque[Row])`: each subscripted base one of `imported`, written with
|
|
225
|
+
builtin types, dotted names and the module's own classes alone (no type variable's name).
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Their names.
|
|
229
|
+
|
|
230
|
+
"""
|
|
231
|
+
plain: frozenset[str] = frozenset(dir(builtins)) | {node.name for node in nodes}
|
|
232
|
+
return frozenset(
|
|
233
|
+
node.name
|
|
234
|
+
for node in nodes
|
|
235
|
+
if not cast("object", getattr(node, "type_params", ())) # Python 3.12+'s `class C[T]`
|
|
236
|
+
and all(
|
|
237
|
+
ast.unparse(base) in imported
|
|
238
|
+
and all(part.id in plain for part in ast.walk(base.slice) if isinstance(part, ast.Name))
|
|
239
|
+
for base in node.bases
|
|
240
|
+
if isinstance(base, ast.Subscript)
|
|
241
|
+
)
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _order(name: str, parents: Mapping[str, list[str]], above: frozenset[str]) -> list[str] | None:
|
|
246
|
+
"""Linearise a class's ancestry as Python does (C3), a base out of sight standing for itself alone.
|
|
247
|
+
|
|
248
|
+
`above`: the classes this one is being ordered for, which it can't be a base of.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
The class, then its ancestors in method resolution order; or `None` if they have no order.
|
|
252
|
+
|
|
253
|
+
"""
|
|
254
|
+
if name not in parents:
|
|
255
|
+
return [name]
|
|
256
|
+
if name in above:
|
|
257
|
+
return None
|
|
258
|
+
orders: list[list[str]] = []
|
|
259
|
+
base: str
|
|
260
|
+
for base in parents[name]:
|
|
261
|
+
found: list[str] | None
|
|
262
|
+
if (found := _order(base, parents, above | {name})) is None:
|
|
263
|
+
return None
|
|
264
|
+
orders.append(found)
|
|
265
|
+
merged: list[str] | None = _merged([*orders, list(parents[name])])
|
|
266
|
+
return None if merged is None else [name, *merged]
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _merged(orders: list[list[str]]) -> list[str] | None:
|
|
270
|
+
"""Merge linearisations, each one's order kept: the first head that no other's tail holds, each time.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
The merged order, or `None` if they disagree.
|
|
274
|
+
|
|
275
|
+
"""
|
|
276
|
+
left: list[list[str]]
|
|
277
|
+
if not (left := [order for order in orders if order]):
|
|
278
|
+
return []
|
|
279
|
+
head: str | None = next(
|
|
280
|
+
(order[0] for order in left if not any(order[0] in other[1:] for other in left)),
|
|
281
|
+
None,
|
|
282
|
+
)
|
|
283
|
+
if head is None:
|
|
284
|
+
return None
|
|
285
|
+
rest: list[str] | None = _merged([order[1:] if order[0] == head else order for order in left])
|
|
286
|
+
return None if rest is None else [head, *rest]
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _bound(body: Sequence[ast.stmt]) -> Iterator[str]:
|
|
290
|
+
"""Name what a class's body binds, through its compound statements: not what its methods do.
|
|
291
|
+
|
|
292
|
+
Yields:
|
|
293
|
+
Each function's and nested class's name, and each name an assignment, an import or any
|
|
294
|
+
other statement stores to.
|
|
295
|
+
|
|
296
|
+
"""
|
|
297
|
+
stmt: ast.stmt
|
|
298
|
+
for stmt in body:
|
|
299
|
+
if isinstance(stmt, (*FUNCTION_DEFS, ast.ClassDef)):
|
|
300
|
+
yield stmt.name
|
|
301
|
+
elif isinstance(stmt, ast.Import | ast.ImportFrom):
|
|
302
|
+
yield from ((alias.asname or alias.name).partition(".")[0] for alias in stmt.names)
|
|
303
|
+
else:
|
|
304
|
+
part: ast.AST
|
|
305
|
+
for part in expressions(stmt):
|
|
306
|
+
yield from (
|
|
307
|
+
node.id
|
|
308
|
+
for node in walk(part)
|
|
309
|
+
if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store)
|
|
310
|
+
)
|
|
311
|
+
yield from _bound(child_statements(stmt))
|
|
Binary file
|