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,335 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""What a loop binds each time round, and the targets of comprehensions, typed as `--fix` types values.
|
|
3
|
+
|
|
4
|
+
`looped` types a `for` loop's target by what its iterable yields; `looped_parts` each part of an
|
|
5
|
+
`enumerate` or `zip` loop's tuples on its own; `comprehended` the targets of a value's
|
|
6
|
+
comprehensions, as a loop's are. `infer` types a value as `constricter.fix.values.inference` does,
|
|
7
|
+
over the names a scope declared.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import ast
|
|
11
|
+
import re
|
|
12
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
13
|
+
from typing import Final, TypeAlias, cast
|
|
14
|
+
|
|
15
|
+
from constricter.fix.core.known import Inference, Known, kinds_of
|
|
16
|
+
from constricter.fix.libraries import overloads, walks
|
|
17
|
+
from constricter.fix.values import called, shapes
|
|
18
|
+
from constricter.fix.values.members import present
|
|
19
|
+
from constricter.fix.values.targets import (
|
|
20
|
+
DICT_VIEWS,
|
|
21
|
+
ENUMERATE,
|
|
22
|
+
FILTER,
|
|
23
|
+
ITER,
|
|
24
|
+
MAP,
|
|
25
|
+
RANGE,
|
|
26
|
+
SAME_ELEMENTS,
|
|
27
|
+
ZIP,
|
|
28
|
+
counted,
|
|
29
|
+
dict_parts,
|
|
30
|
+
element_type,
|
|
31
|
+
iterated,
|
|
32
|
+
iterator_call,
|
|
33
|
+
unpacked,
|
|
34
|
+
)
|
|
35
|
+
from constricter.offences import CONSTRUCTOR
|
|
36
|
+
from constricter.rules.flow import members
|
|
37
|
+
|
|
38
|
+
_GENERIC: Final = "[" # in a type's text: its arguments
|
|
39
|
+
COMPREHENSIONS: Final = (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)
|
|
40
|
+
_UNION: Final = re.compile(r"\||\b(?:None|Optional|Union)\b") # an annotation with a union in it
|
|
41
|
+
_NONE: Final = "None"
|
|
42
|
+
# A predicate's return that narrows what it's given
|
|
43
|
+
_NARROWING: Final = re.compile(r"\b(?:TypeGuard|TypeIs)\b")
|
|
44
|
+
_CALLABLE: Final = "callable" # the builtin predicate that narrows (`TypeIs[Callable[..., object]]`)
|
|
45
|
+
_PREDICATE: Final = 2 # `filter`'s arguments: the predicate and the iterable
|
|
46
|
+
_Comprehension: TypeAlias = ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp
|
|
47
|
+
# Types a value over what a scope declared, as `constricter.fix.values.inference.inference` does.
|
|
48
|
+
Infer: TypeAlias = Callable[[ast.expr, Known, Mapping[str, str]], Inference | None]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# One part of a loop target (see `looped_parts`): its inference, and the values it came from.
|
|
52
|
+
LoopPart: TypeAlias = tuple[Inference | None, list[ast.expr]]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def rebased(receiver: str, name: str, known: Known) -> str:
|
|
56
|
+
"""Read a class of the module's as the generic library class it takes `name` from, given its arguments.
|
|
57
|
+
|
|
58
|
+
`Rows`, under `collections.deque[Row]`, as that: what its instance's subscript, loop or view gives.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
The base as the class writes it; `receiver` itself for any other.
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
base: str | None = known.class_side.lineage.definer(receiver, name)
|
|
65
|
+
return base if base is not None and _GENERIC in base and _GENERIC not in receiver else receiver
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def comprehended(
|
|
69
|
+
value: ast.expr,
|
|
70
|
+
known: Known,
|
|
71
|
+
declared: Mapping[str, str],
|
|
72
|
+
infer: Infer,
|
|
73
|
+
) -> Mapping[str, str]:
|
|
74
|
+
"""Type the targets of every comprehension in `value`, as a loop's are, over what's `declared`.
|
|
75
|
+
|
|
76
|
+
A target whose type isn't known is dropped (it shadows any outer name of the same name).
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
`declared`, with the targets' types: `declared` itself, uncopied, where there's none (most values).
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
return targets_typed(
|
|
83
|
+
[node for node in ast.walk(value) if isinstance(node, COMPREHENSIONS)],
|
|
84
|
+
known,
|
|
85
|
+
declared,
|
|
86
|
+
infer,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def targets_typed(
|
|
91
|
+
comprehensions: Sequence[ast.AST],
|
|
92
|
+
known: Known,
|
|
93
|
+
declared: Mapping[str, str],
|
|
94
|
+
infer: Infer,
|
|
95
|
+
) -> Mapping[str, str]:
|
|
96
|
+
"""Type the targets of `comprehensions`, as a loop's are, over what's `declared` (see `comprehended`).
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
`declared`, with the targets' types: `declared` itself, uncopied, where there are none.
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
if not comprehensions:
|
|
103
|
+
return declared
|
|
104
|
+
inside: dict[str, str] = dict(declared)
|
|
105
|
+
node: ast.AST
|
|
106
|
+
generator: ast.comprehension
|
|
107
|
+
for node in comprehensions:
|
|
108
|
+
for generator in cast("_Comprehension", node).generators:
|
|
109
|
+
found: Inference | None = looped(generator.iter, known, inside, infer)
|
|
110
|
+
name: ast.Name
|
|
111
|
+
part: str | None
|
|
112
|
+
annotation: str | None = None if found is None else found.annotation
|
|
113
|
+
for name, part in unpacked(generator.target, annotation, known.indirect.tuples):
|
|
114
|
+
if part is None:
|
|
115
|
+
_ = inside.pop(name.id, None)
|
|
116
|
+
else:
|
|
117
|
+
inside[name.id] = part
|
|
118
|
+
return inside
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def looped(iterable: ast.expr, known: Known, declared: Mapping[str, str], infer: Infer) -> Inference | None:
|
|
122
|
+
"""Infer what a `for` loop over `iterable` binds each time round, and say how.
|
|
123
|
+
|
|
124
|
+
`range()` gives `int`s; `enumerate(x)` `tuple[int, T]` and `zip(x, y)` `tuple[T, U]`, given
|
|
125
|
+
`x`'s and `y`'s; `reversed(x)`, `sorted(x)` and `iter(x)` what `x` does; `map(f, x)` what `f`
|
|
126
|
+
returns; a generator expression its element; a `dict`'s `.keys()`, `.values()` and `.items()`
|
|
127
|
+
its keys, values and pairs; and anything else
|
|
128
|
+
whose type is inferred, its elements: a `list`, `set`, `frozenset` or `tuple[T, ...]`'s `T`, a
|
|
129
|
+
tuple's parts where they agree, a `dict`'s keys, a `str`'s `str`s and a `bytes`'s `int`s, and a
|
|
130
|
+
standard-library class's by its `__iter__` (a file's lines). `os.walk(top)` gives a directory's
|
|
131
|
+
path, with its directories' names and its files', as `top` is typed (see `walks.walked`).
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
The element's annotation as source text and its reason, or `None` if it isn't known.
|
|
135
|
+
|
|
136
|
+
"""
|
|
137
|
+
call: tuple[str, list[ast.expr]] | None = iterator_call(iterable)
|
|
138
|
+
if call is not None and known.is_builtin(call[0]):
|
|
139
|
+
return _iterator(*call, known, declared, infer)
|
|
140
|
+
view: str
|
|
141
|
+
receiver: ast.expr
|
|
142
|
+
match iterable:
|
|
143
|
+
case ast.Call(func=ast.Attribute(value=receiver, attr=view), args=[]) if view in DICT_VIEWS:
|
|
144
|
+
return dict_view(receiver, view, known, declared, infer)
|
|
145
|
+
case ast.GeneratorExp():
|
|
146
|
+
return _generated(iterable, known, declared, infer)
|
|
147
|
+
case _:
|
|
148
|
+
found: Inference | None
|
|
149
|
+
if (found := infer(iterable, known, declared)) is None:
|
|
150
|
+
return walks.walked(iterable, known, lambda top: infer(top, known, declared))
|
|
151
|
+
kinds: frozenset[str] = kinds_of(found, kind="loop")
|
|
152
|
+
# Of an `X | None`, the `X`'s: `None` has none. A builtin container's or an
|
|
153
|
+
# `Iterable[T]`'s, else a standard-library class's own.
|
|
154
|
+
held: str = rebased(present(found.annotation, ""), "__iter__", known)
|
|
155
|
+
element: Inference | None = overloads.library_element(held, known)
|
|
156
|
+
return element_type(held, f"the elements of {found.reason}", kinds) or (
|
|
157
|
+
None if element is None else element._replace(kinds=kinds | element.kinds)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _iterator(
|
|
162
|
+
name: str,
|
|
163
|
+
args: list[ast.expr],
|
|
164
|
+
known: Known,
|
|
165
|
+
declared: Mapping[str, str],
|
|
166
|
+
infer: Infer,
|
|
167
|
+
) -> Inference | None:
|
|
168
|
+
"""Infer what one of `ITERATORS`, called with `args`, yields.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Its elements' annotation and reason, or `None` if an argument's elements aren't known.
|
|
172
|
+
|
|
173
|
+
"""
|
|
174
|
+
if name == RANGE:
|
|
175
|
+
return Inference("int", "`range`, which yields `int`s", frozenset({"loop"}))
|
|
176
|
+
if name == MAP:
|
|
177
|
+
return _mapped(args[0], known)
|
|
178
|
+
if name == FILTER:
|
|
179
|
+
return _filtered(args, known, declared, infer)
|
|
180
|
+
if name in SAME_ELEMENTS or name == ITER:
|
|
181
|
+
return looped(args[0], known, declared, infer) if name != ITER or len(args) == 1 else None
|
|
182
|
+
parts: list[Inference | None] = [looped(arg, known, declared, infer) for arg in counted(name, args)]
|
|
183
|
+
found: list[Inference] = [part for part in parts if part is not None]
|
|
184
|
+
if len(found) != len(parts):
|
|
185
|
+
return None
|
|
186
|
+
annotations: list[str] = ["int"] * (name == ENUMERATE) + [part.annotation for part in found]
|
|
187
|
+
return Inference(f"tuple[{', '.join(annotations)}]", f"`{name}`'s tuples", kinds_of(*found, kind="loop"))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _filtered(
|
|
191
|
+
args: list[ast.expr],
|
|
192
|
+
known: Known,
|
|
193
|
+
declared: Mapping[str, str],
|
|
194
|
+
infer: Infer,
|
|
195
|
+
) -> Inference | None:
|
|
196
|
+
"""Infer what `filter(predicate, xs)` yields: `xs`'s elements, less `None` for a `None` predicate.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
The inference, or `None`: where `xs`'s elements aren't known, or the predicate may narrow them
|
|
200
|
+
(see `_keeps`).
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
if len(args) != _PREDICATE:
|
|
204
|
+
return None
|
|
205
|
+
found: Inference | None = looped(args[1], known, declared, infer)
|
|
206
|
+
if found is None or not (shapes.is_none(args[0]) or _keeps(args[0], known)):
|
|
207
|
+
return None
|
|
208
|
+
annotation: str = found.annotation
|
|
209
|
+
if shapes.is_none(args[0]):
|
|
210
|
+
types: frozenset[str] | None = members(annotation)
|
|
211
|
+
rest: list[str]
|
|
212
|
+
if not (rest := sorted((types or frozenset()) - {_NONE})):
|
|
213
|
+
return None
|
|
214
|
+
annotation = annotation if types == frozenset(rest) else " | ".join(rest)
|
|
215
|
+
return Inference(annotation, f"`filter`, which keeps {found.reason}", kinds_of(found, kind="loop"))
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _keeps(predicate: ast.expr, known: Known) -> bool:
|
|
219
|
+
"""Check whether `filter`'s `predicate` keeps elements as they are, narrowing none.
|
|
220
|
+
|
|
221
|
+
A `lambda`; a builtin (but `callable`), or a method of a builtin class (`str.isdigit`); or a
|
|
222
|
+
function the module or another checked file defines, declaring a return other than a
|
|
223
|
+
`TypeGuard` or `TypeIs`, or typed by its `return`s.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Whether it does; not for any other callable, which may be declared to narrow.
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
name: str
|
|
230
|
+
match predicate:
|
|
231
|
+
case ast.Lambda():
|
|
232
|
+
return True
|
|
233
|
+
case ast.Name(id=name) if known.is_builtin(name):
|
|
234
|
+
return name != _CALLABLE
|
|
235
|
+
case ast.Attribute(value=ast.Name(id=name)) if known.is_builtin(name):
|
|
236
|
+
return True
|
|
237
|
+
case ast.Name() | ast.Attribute():
|
|
238
|
+
callee: str = ast.unparse(predicate)
|
|
239
|
+
declared: str | None = known.calls.get(callee)
|
|
240
|
+
return (
|
|
241
|
+
declared is not None and not _NARROWING.search(declared)
|
|
242
|
+
) or callee in known.returned.calls
|
|
243
|
+
case _:
|
|
244
|
+
return False
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _generated(
|
|
248
|
+
value: ast.GeneratorExp,
|
|
249
|
+
known: Known,
|
|
250
|
+
declared: Mapping[str, str],
|
|
251
|
+
infer: Infer,
|
|
252
|
+
) -> Inference | None:
|
|
253
|
+
"""Infer what a generator expression yields: its element, its targets typed as a loop's are.
|
|
254
|
+
|
|
255
|
+
Not one with a condition whose element's type has a union in it, which the condition may narrow.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
The inference, or `None`.
|
|
259
|
+
|
|
260
|
+
"""
|
|
261
|
+
found: Inference | None = infer(value.elt, known, comprehended(value, known, declared, infer))
|
|
262
|
+
found = found and shapes.sifted(value, value.elt, found)
|
|
263
|
+
filtered: bool = any(generator.ifs for generator in value.generators)
|
|
264
|
+
if found is None or (filtered and _UNION.search(found.annotation)):
|
|
265
|
+
return None
|
|
266
|
+
loops: list[Inference | None] = [looped(g.iter, known, declared, infer) for g in value.generators]
|
|
267
|
+
return Inference(
|
|
268
|
+
found.annotation,
|
|
269
|
+
"a generator expression's elements",
|
|
270
|
+
kinds_of(found, *loops, kind="comprehension"),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _mapped(function: ast.expr, known: Known) -> Inference | None:
|
|
275
|
+
"""Infer what `map(function, ...)` yields: what `function` returns, whatever it's given.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
A fixed-return builtin's type (`map(int, parts)`), or a function's declared return; `None`
|
|
279
|
+
for any other callable.
|
|
280
|
+
|
|
281
|
+
"""
|
|
282
|
+
call: ast.Call = ast.copy_location(ast.Call(function, [], []), function)
|
|
283
|
+
found: Inference | None = called.typed_call(call, known)
|
|
284
|
+
if found is None or CONSTRUCTOR in found.kinds:
|
|
285
|
+
return None
|
|
286
|
+
return Inference(found.annotation, f"`map`, which yields {found.reason}", kinds_of(found, kind="loop"))
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def looped_parts(
|
|
290
|
+
iterable: ast.expr,
|
|
291
|
+
known: Known,
|
|
292
|
+
declared: Mapping[str, str],
|
|
293
|
+
infer: Infer,
|
|
294
|
+
) -> list[LoopPart] | None:
|
|
295
|
+
"""Infer each part of the tuples `enumerate` or `zip` yields on its own, even if the others aren't known.
|
|
296
|
+
|
|
297
|
+
`enumerate`'s index is always an `int`, whatever it counts.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Each part's inference (`None` if it isn't known), with the values it came from (to judge
|
|
301
|
+
whether it's a guess, see `iterated`); or `None` if `iterable` isn't such a call.
|
|
302
|
+
|
|
303
|
+
"""
|
|
304
|
+
call: tuple[str, list[ast.expr]] | None = iterator_call(iterable)
|
|
305
|
+
if call is None or call[0] not in {ENUMERATE, ZIP} or not known.is_builtin(call[0]):
|
|
306
|
+
return None
|
|
307
|
+
index: list[LoopPart] = (
|
|
308
|
+
[(Inference("int", "`enumerate`'s index", frozenset({"loop"})), [])] if call[0] == ENUMERATE else []
|
|
309
|
+
)
|
|
310
|
+
return index + [(looped(arg, known, declared, infer), iterated(arg)) for arg in counted(*call)]
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def dict_view(
|
|
314
|
+
receiver: ast.expr,
|
|
315
|
+
view: str,
|
|
316
|
+
known: Known,
|
|
317
|
+
declared: Mapping[str, str],
|
|
318
|
+
infer: Infer,
|
|
319
|
+
) -> Inference | None:
|
|
320
|
+
"""Infer the elements of a `dict`'s `.keys()`, `.values()` or `.items()`.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
Them, or `None` if the receiver isn't a `dict` whose type is known.
|
|
324
|
+
|
|
325
|
+
"""
|
|
326
|
+
found: Inference | None = infer(receiver, known, declared)
|
|
327
|
+
types: tuple[str, str] | None
|
|
328
|
+
if (types := None if found is None else dict_parts(rebased(found.annotation, view, known))) is None:
|
|
329
|
+
return None
|
|
330
|
+
by_view: dict[str, str] = {
|
|
331
|
+
"keys": types[0],
|
|
332
|
+
"values": types[1],
|
|
333
|
+
"items": f"tuple[{types[0]}, {types[1]}]",
|
|
334
|
+
}
|
|
335
|
+
return Inference(by_view[view], f"a `dict`'s `.{view}()`", kinds_of(found, kind="loop"))
|
|
Binary file
|