python-constricter 0.4.1__cp314-cp314-win_amd64.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__.cp314-win_amd64.pyd +0 -0
- constricter/fix/__init__.py +2 -0
- constricter/fix/core/__init__.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/__init__.py +2 -0
- constricter/fix/core/asked.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/asked.py +56 -0
- constricter/fix/core/fixes.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/fixes.py +317 -0
- constricter/fix/core/imports.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/imports.py +580 -0
- constricter/fix/core/inherited.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/inherited.py +311 -0
- constricter/fix/core/known.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/known.py +730 -0
- constricter/fix/core/signatures.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/signatures.py +132 -0
- constricter/fix/index/__init__.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/__init__.py +2 -0
- constricter/fix/index/awaits.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/awaits.py +90 -0
- constricter/fix/index/beyond.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/beyond.py +149 -0
- constricter/fix/index/callers.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/callers.py +155 -0
- constricter/fix/index/crossed.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/crossed.py +327 -0
- constricter/fix/index/decorated.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/decorated.py +140 -0
- constricter/fix/index/fixtures.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/fixtures.py +176 -0
- constricter/fix/index/linked.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/linked.py +142 -0
- constricter/fix/index/loose.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/loose.py +103 -0
- constricter/fix/index/managed.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/managed.py +63 -0
- constricter/fix/index/modules.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/modules.py +741 -0
- constricter/fix/index/offers.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/offers.py +252 -0
- constricter/fix/index/order.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/order.py +155 -0
- constricter/fix/index/origins.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/origins.py +238 -0
- constricter/fix/index/own_methods.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/own_methods.py +91 -0
- constricter/fix/index/own_overloads.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/own_overloads.py +70 -0
- constricter/fix/index/own_types.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/own_types.py +330 -0
- constricter/fix/index/params.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/params.py +81 -0
- constricter/fix/index/plain.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/plain.py +221 -0
- constricter/fix/index/project.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/project.py +797 -0
- constricter/fix/index/sides.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/sides.py +160 -0
- constricter/fix/index/strings.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/strings.py +46 -0
- constricter/fix/index/stubs/__init__.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/__init__.py +2 -0
- constricter/fix/index/stubs/atoms.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/atoms.py +425 -0
- constricter/fix/index/stubs/chained.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/chained.py +69 -0
- constricter/fix/index/stubs/classnames.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/classnames.py +124 -0
- constricter/fix/index/stubs/declared.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/declared.py +508 -0
- constricter/fix/index/stubs/installed.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/installed.py +425 -0
- constricter/fix/index/stubs/names.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/names.py +200 -0
- constricter/fix/index/stubs/read.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/read.py +70 -0
- constricter/fix/index/stubs/signatures.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/signatures.py +656 -0
- constricter/fix/index/stubs/stubbed.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/stubbed.py +208 -0
- constricter/fix/index/taken.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/taken.py +45 -0
- constricter/fix/index/tuples.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/tuples.py +112 -0
- constricter/fix/libraries/__init__.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/__init__.py +2 -0
- constricter/fix/libraries/library.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/library.py +420 -0
- constricter/fix/libraries/opened.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/opened.py +147 -0
- constricter/fix/libraries/overloads.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/overloads.py +960 -0
- constricter/fix/libraries/receivers.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/receivers.py +274 -0
- constricter/fix/libraries/stdlib.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/stdlib.py +752 -0
- constricter/fix/libraries/walks.cp314-win_amd64.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__.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/__init__.py +2 -0
- constricter/fix/values/aliased.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/aliased.py +195 -0
- constricter/fix/values/bodies.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/bodies.py +76 -0
- constricter/fix/values/callables.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/callables.py +323 -0
- constricter/fix/values/called.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/called.py +194 -0
- constricter/fix/values/classvars.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/classvars.py +629 -0
- constricter/fix/values/decided.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/decided.py +429 -0
- constricter/fix/values/displays.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/displays.py +101 -0
- constricter/fix/values/doubts.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/doubts.py +677 -0
- constricter/fix/values/entered.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/entered.py +332 -0
- constricter/fix/values/fills.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/fills.py +446 -0
- constricter/fix/values/guesses.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/guesses.py +376 -0
- constricter/fix/values/hinted.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/hinted.py +811 -0
- constricter/fix/values/inference.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/inference.py +698 -0
- constricter/fix/values/loops.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/loops.py +335 -0
- constricter/fix/values/members.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/members.py +578 -0
- constricter/fix/values/narrowed.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/narrowed.py +220 -0
- constricter/fix/values/operated.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/operated.py +202 -0
- constricter/fix/values/ordered.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/ordered.py +74 -0
- constricter/fix/values/returned.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/returned.py +479 -0
- constricter/fix/values/returns.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/returns.py +237 -0
- constricter/fix/values/shapes.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/shapes.py +654 -0
- constricter/fix/values/stored.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/stored.py +564 -0
- constricter/fix/values/targets.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/targets.py +419 -0
- constricter/fix/values/unchecked.cp314-win_amd64.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__.cp314-win_amd64.pyd +0 -0
- constricter/rules/__init__.py +2 -0
- constricter/rules/annotations.cp314-win_amd64.pyd +0 -0
- constricter/rules/annotations.py +296 -0
- constricter/rules/binding.cp314-win_amd64.pyd +0 -0
- constricter/rules/binding.py +652 -0
- constricter/rules/calls.cp314-win_amd64.pyd +0 -0
- constricter/rules/calls.py +460 -0
- constricter/rules/checker.cp314-win_amd64.pyd +0 -0
- constricter/rules/checker.py +536 -0
- constricter/rules/declarations.cp314-win_amd64.pyd +0 -0
- constricter/rules/declarations.py +730 -0
- constricter/rules/decorators.cp314-win_amd64.pyd +0 -0
- constricter/rules/decorators.py +351 -0
- constricter/rules/flow.cp314-win_amd64.pyd +0 -0
- constricter/rules/flow.py +502 -0
- constricter/rules/keys.cp314-win_amd64.pyd +0 -0
- constricter/rules/keys.py +79 -0
- constricter/rules/late.cp314-win_amd64.pyd +0 -0
- constricter/rules/late.py +969 -0
- constricter/rules/narrowing.cp314-win_amd64.pyd +0 -0
- constricter/rules/narrowing.py +98 -0
- constricter/rules/parsed.cp314-win_amd64.pyd +0 -0
- constricter/rules/parsed.py +136 -0
- constricter/rules/quoted.cp314-win_amd64.pyd +0 -0
- constricter/rules/quoted.py +141 -0
- constricter/rules/rebinding.cp314-win_amd64.pyd +0 -0
- constricter/rules/rebinding.py +169 -0
- constricter/rules/recorded.cp314-win_amd64.pyd +0 -0
- constricter/rules/recorded.py +168 -0
- constricter/rules/redundant.cp314-win_amd64.pyd +0 -0
- constricter/rules/redundant.py +160 -0
- constricter/rules/scope.cp314-win_amd64.pyd +0 -0
- constricter/rules/scope.py +802 -0
- constricter/rules/scoped.cp314-win_amd64.pyd +0 -0
- constricter/rules/scoped.py +232 -0
- constricter/rules/scoping.cp314-win_amd64.pyd +0 -0
- constricter/rules/scoping.py +346 -0
- constricter/rules/settings.cp314-win_amd64.pyd +0 -0
- constricter/rules/settings.py +189 -0
- constricter/rules/syntax.cp314-win_amd64.pyd +0 -0
- constricter/rules/syntax.py +472 -0
- constricter/rules/tables.cp314-win_amd64.pyd +0 -0
- constricter/rules/tables.py +169 -0
- constricter/rules/walked.cp314-win_amd64.pyd +0 -0
- constricter/rules/walked.py +127 -0
- constricter/rules/widened.cp314-win_amd64.pyd +0 -0
- constricter/rules/widened.py +322 -0
- constricter/trace.py +374 -0
- e45ce06b2fdd5c43f782__mypyc.cp314-win_amd64.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,296 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""What an annotation says (LVA005, LVA006, LVA011).
|
|
3
|
+
|
|
4
|
+
An annotation's vague parts, nesting depth and longest fixed-length tuple, the names it's written
|
|
5
|
+
with, and whether it's free of some. What a module declares is `constricter.rules.declarations`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import ast
|
|
9
|
+
import re
|
|
10
|
+
from collections.abc import Mapping
|
|
11
|
+
from functools import lru_cache
|
|
12
|
+
from typing import Final
|
|
13
|
+
|
|
14
|
+
from constricter.rules.quoted import parsed
|
|
15
|
+
|
|
16
|
+
_VAGUE: Final = frozenset({"Any", "object"})
|
|
17
|
+
_UNIONS: Final = frozenset({"Optional", "Union"}) # a union's members, as a subscript's arguments
|
|
18
|
+
# `collections.abc`'s generic classes (`typing` has each too).
|
|
19
|
+
ABSTRACT: Final = frozenset(
|
|
20
|
+
{
|
|
21
|
+
"AsyncGenerator",
|
|
22
|
+
"AsyncIterable",
|
|
23
|
+
"AsyncIterator",
|
|
24
|
+
"Awaitable",
|
|
25
|
+
"Callable",
|
|
26
|
+
"Collection",
|
|
27
|
+
"Container",
|
|
28
|
+
"Coroutine",
|
|
29
|
+
"Generator",
|
|
30
|
+
"ItemsView",
|
|
31
|
+
"Iterable",
|
|
32
|
+
"Iterator",
|
|
33
|
+
"KeysView",
|
|
34
|
+
"Mapping",
|
|
35
|
+
"MutableMapping",
|
|
36
|
+
"MutableSequence",
|
|
37
|
+
"MutableSet",
|
|
38
|
+
"Reversible",
|
|
39
|
+
"Sequence",
|
|
40
|
+
"ValuesView",
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
# Generics that say little without their parameters.
|
|
44
|
+
GENERICS: Final = ABSTRACT | frozenset(
|
|
45
|
+
{
|
|
46
|
+
"AbstractSet",
|
|
47
|
+
"ChainMap",
|
|
48
|
+
"Counter",
|
|
49
|
+
"DefaultDict",
|
|
50
|
+
"Deque",
|
|
51
|
+
"Dict",
|
|
52
|
+
"FrozenSet",
|
|
53
|
+
"List",
|
|
54
|
+
"Match",
|
|
55
|
+
"OrderedDict",
|
|
56
|
+
"Pattern",
|
|
57
|
+
"Set",
|
|
58
|
+
"Tuple",
|
|
59
|
+
"Type",
|
|
60
|
+
"defaultdict",
|
|
61
|
+
"deque",
|
|
62
|
+
"dict",
|
|
63
|
+
"frozenset",
|
|
64
|
+
"list",
|
|
65
|
+
"set",
|
|
66
|
+
"tuple",
|
|
67
|
+
"type",
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
_TUPLES: Final = frozenset({"tuple", "Tuple"}) # the annotations that list one type per element
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def is_composite(value: ast.expr) -> bool:
|
|
74
|
+
"""Check whether a value is written as a type made of others: a subscript, or a union.
|
|
75
|
+
|
|
76
|
+
What a type alias's assignment binds (`Json = dict[str, "Json"] | str`), but for a bare class's
|
|
77
|
+
(`Alias = Class`).
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Whether it is.
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
return isinstance(value, ast.Subscript) or (
|
|
84
|
+
isinstance(value, ast.BinOp) and isinstance(value.op, ast.BitOr)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def node_name(node: ast.AST) -> str:
|
|
89
|
+
"""Read a `Name`'s or `Attribute`'s simple name.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
It, or `""` if `node` is neither.
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
name: str
|
|
96
|
+
match node:
|
|
97
|
+
case ast.Name(id=name) | ast.Attribute(attr=name):
|
|
98
|
+
return name
|
|
99
|
+
case _:
|
|
100
|
+
return ""
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def dotted(node: ast.expr) -> str | None:
|
|
104
|
+
"""Spell a name, or a chain of attributes on one (`pkg.util.f`), as written.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
It, or `None` if `node` is anything else (`f().g`, `x[0].g`).
|
|
108
|
+
|
|
109
|
+
"""
|
|
110
|
+
name: str
|
|
111
|
+
value: ast.expr
|
|
112
|
+
base: str | None
|
|
113
|
+
match node:
|
|
114
|
+
case ast.Name(id=name):
|
|
115
|
+
return name
|
|
116
|
+
case ast.Attribute(value=value, attr=name) if (base := dotted(value)) is not None:
|
|
117
|
+
return f"{base}.{name}"
|
|
118
|
+
case _:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def is_vague(annotation: ast.expr) -> bool:
|
|
123
|
+
"""Check an annotation for vague types.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Whether it has `Any`, `object` or a generic without its parameters in it.
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
root: ast.expr = parsed(annotation)
|
|
130
|
+
subscripted: set[int] = {id(node.value) for node in ast.walk(root) if isinstance(node, ast.Subscript)}
|
|
131
|
+
node: ast.AST
|
|
132
|
+
for node in ast.walk(root):
|
|
133
|
+
name: str = node_name(node)
|
|
134
|
+
if name in _VAGUE or (name in GENERICS and id(node) not in subscripted):
|
|
135
|
+
return True
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def vague_parts(annotation: ast.expr) -> tuple[int, bool]:
|
|
140
|
+
"""Count an annotation's vague parts (see `is_vague`), and say whether one stands alone.
|
|
141
|
+
|
|
142
|
+
Alone: the annotation itself or a member of its union (`Any`, `Any | None`, `Optional[object]`),
|
|
143
|
+
where it says nothing at all. Anywhere else it's inside a type that says the rest
|
|
144
|
+
(`tuple[str, Any]`), as a generic without its parameters is (`list`).
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
How many there are, and whether one does.
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
root: ast.expr = parsed(annotation)
|
|
151
|
+
subscripted: set[int] = {id(node.value) for node in ast.walk(root) if isinstance(node, ast.Subscript)}
|
|
152
|
+
count: int = sum(
|
|
153
|
+
node_name(node) in _VAGUE or (node_name(node) in GENERICS and id(node) not in subscripted)
|
|
154
|
+
for node in ast.walk(root)
|
|
155
|
+
)
|
|
156
|
+
return count, any(node_name(member) in _VAGUE for member in _union_members(root))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _union_members(node: ast.expr) -> list[ast.expr]:
|
|
160
|
+
"""Flatten an annotation's outermost union: `A | B`, `Optional[A]`, `Union[A, B]`.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Its members; the annotation itself, if it's no union.
|
|
164
|
+
|
|
165
|
+
"""
|
|
166
|
+
left: ast.expr
|
|
167
|
+
right: ast.expr
|
|
168
|
+
head: ast.expr
|
|
169
|
+
inner: ast.expr
|
|
170
|
+
match node:
|
|
171
|
+
case ast.BinOp(op=ast.BitOr(), left=left, right=right):
|
|
172
|
+
return [*_union_members(left), *_union_members(right)]
|
|
173
|
+
case ast.Subscript(value=head, slice=inner) if node_name(head) in _UNIONS:
|
|
174
|
+
parts: list[ast.expr] = inner.elts if isinstance(inner, ast.Tuple) else [inner]
|
|
175
|
+
return [member for part in parts for member in _union_members(part)]
|
|
176
|
+
case _:
|
|
177
|
+
return [node]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def vague_fits(annotation: ast.expr, level: int) -> bool:
|
|
181
|
+
"""Check whether an annotation is no vaguer than `level` allows (`vague`, LVA005's).
|
|
182
|
+
|
|
183
|
+
Below 0, no vague part at all. At 0, one, inside a type that says the rest (`tuple[str, Any]`).
|
|
184
|
+
From 1, `level + 1` of them (`tuple[Any, Any]` at 1), and one alone (`Any`).
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Whether it is.
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
count: int
|
|
191
|
+
alone: bool
|
|
192
|
+
count, alone = vague_parts(annotation)
|
|
193
|
+
return not count or (count <= level + 1 and (level > 0 or not alone))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def length(annotation: ast.expr) -> int:
|
|
197
|
+
"""Measure the longest fixed-length tuple an annotation lists, one type per element.
|
|
198
|
+
|
|
199
|
+
`tuple[int, str]` is 2; `tuple[int, ...]` (any length) and anything but a tuple are 0.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
The most elements any `tuple[...]` or `Tuple[...]` in it lists.
|
|
203
|
+
|
|
204
|
+
"""
|
|
205
|
+
node: ast.AST
|
|
206
|
+
head: ast.expr
|
|
207
|
+
elements: list[ast.expr]
|
|
208
|
+
lengths: list[int] = [0]
|
|
209
|
+
for node in ast.walk(parsed(annotation)):
|
|
210
|
+
match node:
|
|
211
|
+
case ast.Subscript(value=head, slice=ast.Tuple(elts=elements)) if node_name(head) in _TUPLES:
|
|
212
|
+
lengths.append(0 if _variadic(elements) else len(elements))
|
|
213
|
+
case ast.Subscript(value=head) if node_name(head) in _TUPLES:
|
|
214
|
+
lengths.append(1)
|
|
215
|
+
case _:
|
|
216
|
+
pass
|
|
217
|
+
return max(lengths)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _variadic(elements: list[ast.expr]) -> bool:
|
|
221
|
+
"""Check whether a tuple annotation's elements end in `...` (`tuple[int, ...]`: any length).
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Whether they do.
|
|
225
|
+
|
|
226
|
+
"""
|
|
227
|
+
# `tuple[()]` (the empty tuple) has no elements at all.
|
|
228
|
+
return bool(elements) and isinstance(elements[-1], ast.Constant) and elements[-1].value is Ellipsis
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def depth(annotation: ast.expr) -> int:
|
|
232
|
+
"""Measure how deeply an annotation's subscripts nest.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
The depth: `dict[str, list[int]]` is 2.
|
|
236
|
+
|
|
237
|
+
"""
|
|
238
|
+
node: ast.expr = parsed(annotation)
|
|
239
|
+
inner: ast.expr
|
|
240
|
+
parts: list[ast.expr]
|
|
241
|
+
left: ast.expr
|
|
242
|
+
right: ast.expr
|
|
243
|
+
match node:
|
|
244
|
+
case ast.Subscript(slice=inner):
|
|
245
|
+
return 1 + depth(inner)
|
|
246
|
+
case ast.Tuple(elts=parts) | ast.List(elts=parts):
|
|
247
|
+
return max((depth(part) for part in parts), default=0)
|
|
248
|
+
case ast.BinOp(left=left, right=right):
|
|
249
|
+
return max(depth(left), depth(right))
|
|
250
|
+
case _:
|
|
251
|
+
return 0
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def free_of(types: Mapping[str, str], type_vars: frozenset[str]) -> dict[str, str]:
|
|
255
|
+
"""Drop the types that mention one of `type_vars` (a call's type then depends on its arguments).
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
The others.
|
|
259
|
+
|
|
260
|
+
"""
|
|
261
|
+
return {name: annotation for name, annotation in types.items() if not type_vars & set(words(annotation))}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def free_of_all(
|
|
265
|
+
tables: Mapping[str, Mapping[str, str]],
|
|
266
|
+
type_vars: frozenset[str],
|
|
267
|
+
) -> dict[str, dict[str, str]]:
|
|
268
|
+
"""Drop each class's member types that mention one of `type_vars` (see `free_of`).
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
The others, per class.
|
|
272
|
+
|
|
273
|
+
"""
|
|
274
|
+
return {owner: free_of(types, type_vars) for owner, types in tables.items()}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def words(annotation: str) -> list[str]:
|
|
278
|
+
"""Split an annotation into the words it's written with (`dict[str, Row]`: `dict`, `str`, `Row`).
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Them, in order.
|
|
282
|
+
|
|
283
|
+
"""
|
|
284
|
+
return [word for word in re.split(r"\W+", annotation) if word]
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@lru_cache(maxsize=4096) # asked of each fix's annotation several times: a few thousand distinct
|
|
288
|
+
def roots(annotation: str) -> frozenset[str]:
|
|
289
|
+
"""Find the names an annotation (maybe a string one) is written with.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
The names: `m.Row` gives `m`; none for a string that isn't an expression.
|
|
293
|
+
|
|
294
|
+
"""
|
|
295
|
+
tree: ast.expr = parsed(ast.parse(annotation, mode="eval").body)
|
|
296
|
+
return frozenset(node.id for node in ast.walk(tree) if isinstance(node, ast.Name))
|
|
Binary file
|