python-constricter 0.4.1__cp313-cp313-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__.cp313-win32.pyd +0 -0
- constricter/fix/__init__.py +2 -0
- constricter/fix/core/__init__.cp313-win32.pyd +0 -0
- constricter/fix/core/__init__.py +2 -0
- constricter/fix/core/asked.cp313-win32.pyd +0 -0
- constricter/fix/core/asked.py +56 -0
- constricter/fix/core/fixes.cp313-win32.pyd +0 -0
- constricter/fix/core/fixes.py +317 -0
- constricter/fix/core/imports.cp313-win32.pyd +0 -0
- constricter/fix/core/imports.py +580 -0
- constricter/fix/core/inherited.cp313-win32.pyd +0 -0
- constricter/fix/core/inherited.py +311 -0
- constricter/fix/core/known.cp313-win32.pyd +0 -0
- constricter/fix/core/known.py +730 -0
- constricter/fix/core/signatures.cp313-win32.pyd +0 -0
- constricter/fix/core/signatures.py +132 -0
- constricter/fix/index/__init__.cp313-win32.pyd +0 -0
- constricter/fix/index/__init__.py +2 -0
- constricter/fix/index/awaits.cp313-win32.pyd +0 -0
- constricter/fix/index/awaits.py +90 -0
- constricter/fix/index/beyond.cp313-win32.pyd +0 -0
- constricter/fix/index/beyond.py +149 -0
- constricter/fix/index/callers.cp313-win32.pyd +0 -0
- constricter/fix/index/callers.py +155 -0
- constricter/fix/index/crossed.cp313-win32.pyd +0 -0
- constricter/fix/index/crossed.py +327 -0
- constricter/fix/index/decorated.cp313-win32.pyd +0 -0
- constricter/fix/index/decorated.py +140 -0
- constricter/fix/index/fixtures.cp313-win32.pyd +0 -0
- constricter/fix/index/fixtures.py +176 -0
- constricter/fix/index/linked.cp313-win32.pyd +0 -0
- constricter/fix/index/linked.py +142 -0
- constricter/fix/index/loose.cp313-win32.pyd +0 -0
- constricter/fix/index/loose.py +103 -0
- constricter/fix/index/managed.cp313-win32.pyd +0 -0
- constricter/fix/index/managed.py +63 -0
- constricter/fix/index/modules.cp313-win32.pyd +0 -0
- constricter/fix/index/modules.py +741 -0
- constricter/fix/index/offers.cp313-win32.pyd +0 -0
- constricter/fix/index/offers.py +252 -0
- constricter/fix/index/order.cp313-win32.pyd +0 -0
- constricter/fix/index/order.py +155 -0
- constricter/fix/index/origins.cp313-win32.pyd +0 -0
- constricter/fix/index/origins.py +238 -0
- constricter/fix/index/own_methods.cp313-win32.pyd +0 -0
- constricter/fix/index/own_methods.py +91 -0
- constricter/fix/index/own_overloads.cp313-win32.pyd +0 -0
- constricter/fix/index/own_overloads.py +70 -0
- constricter/fix/index/own_types.cp313-win32.pyd +0 -0
- constricter/fix/index/own_types.py +330 -0
- constricter/fix/index/params.cp313-win32.pyd +0 -0
- constricter/fix/index/params.py +81 -0
- constricter/fix/index/plain.cp313-win32.pyd +0 -0
- constricter/fix/index/plain.py +221 -0
- constricter/fix/index/project.cp313-win32.pyd +0 -0
- constricter/fix/index/project.py +797 -0
- constricter/fix/index/sides.cp313-win32.pyd +0 -0
- constricter/fix/index/sides.py +160 -0
- constricter/fix/index/strings.cp313-win32.pyd +0 -0
- constricter/fix/index/strings.py +46 -0
- constricter/fix/index/stubs/__init__.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/__init__.py +2 -0
- constricter/fix/index/stubs/atoms.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/atoms.py +425 -0
- constricter/fix/index/stubs/chained.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/chained.py +69 -0
- constricter/fix/index/stubs/classnames.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/classnames.py +124 -0
- constricter/fix/index/stubs/declared.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/declared.py +508 -0
- constricter/fix/index/stubs/installed.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/installed.py +425 -0
- constricter/fix/index/stubs/names.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/names.py +200 -0
- constricter/fix/index/stubs/read.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/read.py +70 -0
- constricter/fix/index/stubs/signatures.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/signatures.py +656 -0
- constricter/fix/index/stubs/stubbed.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/stubbed.py +208 -0
- constricter/fix/index/taken.cp313-win32.pyd +0 -0
- constricter/fix/index/taken.py +45 -0
- constricter/fix/index/tuples.cp313-win32.pyd +0 -0
- constricter/fix/index/tuples.py +112 -0
- constricter/fix/libraries/__init__.cp313-win32.pyd +0 -0
- constricter/fix/libraries/__init__.py +2 -0
- constricter/fix/libraries/library.cp313-win32.pyd +0 -0
- constricter/fix/libraries/library.py +420 -0
- constricter/fix/libraries/opened.cp313-win32.pyd +0 -0
- constricter/fix/libraries/opened.py +147 -0
- constricter/fix/libraries/overloads.cp313-win32.pyd +0 -0
- constricter/fix/libraries/overloads.py +960 -0
- constricter/fix/libraries/receivers.cp313-win32.pyd +0 -0
- constricter/fix/libraries/receivers.py +274 -0
- constricter/fix/libraries/stdlib.cp313-win32.pyd +0 -0
- constricter/fix/libraries/stdlib.py +752 -0
- constricter/fix/libraries/walks.cp313-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__.cp313-win32.pyd +0 -0
- constricter/fix/values/__init__.py +2 -0
- constricter/fix/values/aliased.cp313-win32.pyd +0 -0
- constricter/fix/values/aliased.py +195 -0
- constricter/fix/values/bodies.cp313-win32.pyd +0 -0
- constricter/fix/values/bodies.py +76 -0
- constricter/fix/values/callables.cp313-win32.pyd +0 -0
- constricter/fix/values/callables.py +323 -0
- constricter/fix/values/called.cp313-win32.pyd +0 -0
- constricter/fix/values/called.py +194 -0
- constricter/fix/values/classvars.cp313-win32.pyd +0 -0
- constricter/fix/values/classvars.py +629 -0
- constricter/fix/values/decided.cp313-win32.pyd +0 -0
- constricter/fix/values/decided.py +429 -0
- constricter/fix/values/displays.cp313-win32.pyd +0 -0
- constricter/fix/values/displays.py +101 -0
- constricter/fix/values/doubts.cp313-win32.pyd +0 -0
- constricter/fix/values/doubts.py +677 -0
- constricter/fix/values/entered.cp313-win32.pyd +0 -0
- constricter/fix/values/entered.py +332 -0
- constricter/fix/values/fills.cp313-win32.pyd +0 -0
- constricter/fix/values/fills.py +446 -0
- constricter/fix/values/guesses.cp313-win32.pyd +0 -0
- constricter/fix/values/guesses.py +376 -0
- constricter/fix/values/hinted.cp313-win32.pyd +0 -0
- constricter/fix/values/hinted.py +811 -0
- constricter/fix/values/inference.cp313-win32.pyd +0 -0
- constricter/fix/values/inference.py +698 -0
- constricter/fix/values/loops.cp313-win32.pyd +0 -0
- constricter/fix/values/loops.py +335 -0
- constricter/fix/values/members.cp313-win32.pyd +0 -0
- constricter/fix/values/members.py +578 -0
- constricter/fix/values/narrowed.cp313-win32.pyd +0 -0
- constricter/fix/values/narrowed.py +220 -0
- constricter/fix/values/operated.cp313-win32.pyd +0 -0
- constricter/fix/values/operated.py +202 -0
- constricter/fix/values/ordered.cp313-win32.pyd +0 -0
- constricter/fix/values/ordered.py +74 -0
- constricter/fix/values/returned.cp313-win32.pyd +0 -0
- constricter/fix/values/returned.py +479 -0
- constricter/fix/values/returns.cp313-win32.pyd +0 -0
- constricter/fix/values/returns.py +237 -0
- constricter/fix/values/shapes.cp313-win32.pyd +0 -0
- constricter/fix/values/shapes.py +654 -0
- constricter/fix/values/stored.cp313-win32.pyd +0 -0
- constricter/fix/values/stored.py +564 -0
- constricter/fix/values/targets.cp313-win32.pyd +0 -0
- constricter/fix/values/targets.py +419 -0
- constricter/fix/values/unchecked.cp313-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__.cp313-win32.pyd +0 -0
- constricter/rules/__init__.py +2 -0
- constricter/rules/annotations.cp313-win32.pyd +0 -0
- constricter/rules/annotations.py +296 -0
- constricter/rules/binding.cp313-win32.pyd +0 -0
- constricter/rules/binding.py +652 -0
- constricter/rules/calls.cp313-win32.pyd +0 -0
- constricter/rules/calls.py +460 -0
- constricter/rules/checker.cp313-win32.pyd +0 -0
- constricter/rules/checker.py +536 -0
- constricter/rules/declarations.cp313-win32.pyd +0 -0
- constricter/rules/declarations.py +730 -0
- constricter/rules/decorators.cp313-win32.pyd +0 -0
- constricter/rules/decorators.py +351 -0
- constricter/rules/flow.cp313-win32.pyd +0 -0
- constricter/rules/flow.py +502 -0
- constricter/rules/keys.cp313-win32.pyd +0 -0
- constricter/rules/keys.py +79 -0
- constricter/rules/late.cp313-win32.pyd +0 -0
- constricter/rules/late.py +969 -0
- constricter/rules/narrowing.cp313-win32.pyd +0 -0
- constricter/rules/narrowing.py +98 -0
- constricter/rules/parsed.cp313-win32.pyd +0 -0
- constricter/rules/parsed.py +136 -0
- constricter/rules/quoted.cp313-win32.pyd +0 -0
- constricter/rules/quoted.py +141 -0
- constricter/rules/rebinding.cp313-win32.pyd +0 -0
- constricter/rules/rebinding.py +169 -0
- constricter/rules/recorded.cp313-win32.pyd +0 -0
- constricter/rules/recorded.py +168 -0
- constricter/rules/redundant.cp313-win32.pyd +0 -0
- constricter/rules/redundant.py +160 -0
- constricter/rules/scope.cp313-win32.pyd +0 -0
- constricter/rules/scope.py +802 -0
- constricter/rules/scoped.cp313-win32.pyd +0 -0
- constricter/rules/scoped.py +232 -0
- constricter/rules/scoping.cp313-win32.pyd +0 -0
- constricter/rules/scoping.py +346 -0
- constricter/rules/settings.cp313-win32.pyd +0 -0
- constricter/rules/settings.py +189 -0
- constricter/rules/syntax.cp313-win32.pyd +0 -0
- constricter/rules/syntax.py +472 -0
- constricter/rules/tables.cp313-win32.pyd +0 -0
- constricter/rules/tables.py +169 -0
- constricter/rules/walked.cp313-win32.pyd +0 -0
- constricter/rules/walked.py +127 -0
- constricter/rules/widened.cp313-win32.pyd +0 -0
- constricter/rules/widened.py +322 -0
- constricter/trace.py +374 -0
- e45ce06b2fdd5c43f782__mypyc.cp313-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,730 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""What `--fix` knows: a module's declarations it infers from (`Known`), and what it infers (`Inference`)."""
|
|
3
|
+
|
|
4
|
+
import ast
|
|
5
|
+
import builtins
|
|
6
|
+
import copy
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
from collections.abc import Mapping
|
|
10
|
+
from dataclasses import dataclass, field, fields
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from types import MappingProxyType
|
|
13
|
+
from typing import TYPE_CHECKING, Final, NamedTuple, TypeAlias, cast
|
|
14
|
+
|
|
15
|
+
from constricter.empty import Empty
|
|
16
|
+
from constricter.fix.core.inherited import Beyond, Lineage
|
|
17
|
+
from constricter.fix.core.signatures import Expansion, ReadSignature
|
|
18
|
+
from constricter.offences import MAX_LENGTH, VAGUE
|
|
19
|
+
from constricter.overrides import override
|
|
20
|
+
from constricter.rules.annotations import free_of, free_of_all, roots
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from _typeshed import DataclassInstance
|
|
24
|
+
|
|
25
|
+
_BUILTINS: Final = frozenset(dir(builtins))
|
|
26
|
+
_DOT: Final = "."
|
|
27
|
+
# The standard library's modules only some platforms have: a checker on another finds nothing in
|
|
28
|
+
# one. Generated from typeshed (`stdlib_tables`).
|
|
29
|
+
_PLATFORM: Final = frozenset(
|
|
30
|
+
cast(
|
|
31
|
+
"dict[str, str]",
|
|
32
|
+
json.loads(Path(__file__).parents[1].joinpath("tables", "platform.json").read_bytes()),
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
_NAME: Final = re.compile(r"[A-Za-z_]\w*") # each name in a return's template
|
|
36
|
+
# What a name refers to: a module and an attribute of it (`None`: the module itself).
|
|
37
|
+
Origin: TypeAlias = tuple[str, str | None]
|
|
38
|
+
# How a return template starts that's the type itself, as the module calling it writes it: a
|
|
39
|
+
# checked file's overload's (see `constricter.fix.index.stubs.stubbed.overloaded`).
|
|
40
|
+
SPELLED: Final = "="
|
|
41
|
+
# After a fixture's `request` parameter's name: the key its `request.param` is typed under (see `Typed`).
|
|
42
|
+
PARAM: Final = ".param"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# A `dict` itself, not a `UserDict`: every inference reads it, as fast as a `dict` is read.
|
|
46
|
+
class Typed(dict[str, str]): # ruff: ignore[subclass-builtin]
|
|
47
|
+
"""A scope's names' types so far, counting each change: what's inferred of a value holds till then.
|
|
48
|
+
|
|
49
|
+
`held`: the names bound to a call whose type no annotation can write (pytest's `CaptureResult`),
|
|
50
|
+
each with the call, which an attribute read of the name is typed by (see `stood`).
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, types: Mapping[str, str] | None = None, /) -> None:
|
|
54
|
+
"""Start with `types`, counted as none."""
|
|
55
|
+
super().__init__(types or {})
|
|
56
|
+
self.version: int = 0
|
|
57
|
+
self.held: Mapping[str, ast.expr] = MappingProxyType({})
|
|
58
|
+
|
|
59
|
+
@override
|
|
60
|
+
def __setitem__(self, name: str, annotation: str) -> None:
|
|
61
|
+
"""Type `name`, and count it."""
|
|
62
|
+
self.version += 1
|
|
63
|
+
super().__setitem__(name, annotation)
|
|
64
|
+
|
|
65
|
+
@override
|
|
66
|
+
def setdefault(self, name: str, annotation: str = "", /) -> str:
|
|
67
|
+
"""Type `name` if nothing has, and count it.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Its type.
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
self.version += 1
|
|
74
|
+
return super().setdefault(name, annotation)
|
|
75
|
+
|
|
76
|
+
def taking(self, others: Mapping[str, str]) -> None:
|
|
77
|
+
"""Type each of `others`' names, and count it."""
|
|
78
|
+
self.version += 1
|
|
79
|
+
super().update(others)
|
|
80
|
+
|
|
81
|
+
def forget(self, name: str) -> None:
|
|
82
|
+
"""Leave `name` with no type, and count it."""
|
|
83
|
+
self.version += 1
|
|
84
|
+
_ = super().pop(name, None)
|
|
85
|
+
|
|
86
|
+
def hold(self, name: str, call: ast.expr | None) -> None:
|
|
87
|
+
"""Take `name` to stand for `call`, whose type no annotation can write; for nothing, with `None`."""
|
|
88
|
+
if call is None and name not in self.held:
|
|
89
|
+
return
|
|
90
|
+
self.version += 1
|
|
91
|
+
kept: dict[str, ast.expr] = {key: value for key, value in self.held.items() if key != name}
|
|
92
|
+
self.held = kept if call is None else {**kept, name: call}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _held(node: ast.AST, held: Mapping[str, ast.expr]) -> ast.expr | None:
|
|
96
|
+
name: str
|
|
97
|
+
match node:
|
|
98
|
+
case ast.Attribute(value=ast.Name(id=name)) if name in held:
|
|
99
|
+
return held[name]
|
|
100
|
+
case _:
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def stood(value: ast.expr, declared: Mapping[str, str]) -> ast.expr:
|
|
105
|
+
"""Write `value` with the call each name `declared` holds stands for, where it reads an attribute of one.
|
|
106
|
+
|
|
107
|
+
`both.out` as `capsys.readouterr().out`, after `both = capsys.readouterr()` (see `Typed.held`).
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
It: `value` itself where it reads none (most values), else a copy.
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
held: Mapping[str, ast.expr] = declared.held if isinstance(declared, Typed) else {}
|
|
114
|
+
if not held or all(_held(node, held) is None for node in ast.walk(value)):
|
|
115
|
+
return value
|
|
116
|
+
copied: ast.expr = copy.deepcopy(value)
|
|
117
|
+
node: ast.AST
|
|
118
|
+
for node in ast.walk(copied):
|
|
119
|
+
call: ast.expr | None
|
|
120
|
+
if (call := _held(node, held)) is not None:
|
|
121
|
+
cast("ast.Attribute", node).value = call
|
|
122
|
+
return copied
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class Guarded(NamedTuple):
|
|
126
|
+
"""A name a file can write only in an annotation: imported under `if TYPE_CHECKING:` (see `project`).
|
|
127
|
+
|
|
128
|
+
`origin`: what it refers to; `statement`: the import to add for it, or `None` if the file has it.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
origin: Origin
|
|
132
|
+
statement: str | None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class Checking(NamedTuple):
|
|
136
|
+
"""A module's imports for type checking alone: where they go, whose types need one, and its own.
|
|
137
|
+
|
|
138
|
+
`block`: the first and last line of the body of the `if TYPE_CHECKING:` among its leading
|
|
139
|
+
imports, if it has one (else zeros); `lazy`: the top-level packages only its functions import,
|
|
140
|
+
which may not be there to import when the module is (another platform's `pwd`); `bound`: what
|
|
141
|
+
the imports under its top-level `if TYPE_CHECKING:`s bind, each name's dotted origin.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
block: tuple[int, int] = (0, 0)
|
|
145
|
+
lazy: frozenset[str] = frozenset()
|
|
146
|
+
bound: Mapping[str, str] = MappingProxyType({})
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class Classes(NamedTuple):
|
|
150
|
+
"""Classes' instance attributes (and properties) and methods' returns, by the class's name as spelled.
|
|
151
|
+
|
|
152
|
+
What `classes` and `method_returns` read from a module, and `crossed.imported` adds for the
|
|
153
|
+
classes a file imports from other checked files.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
attributes: Mapping[str, Mapping[str, str]]
|
|
157
|
+
methods: Mapping[str, Mapping[str, str]]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass
|
|
161
|
+
class ImportPlan:
|
|
162
|
+
"""How a module can name a library type, and the imports that takes (see `fix.core.imports.plan`).
|
|
163
|
+
|
|
164
|
+
`bound`: each name its imports bind, and what that is (`io`, `io.BytesIO`); `taken`: every name
|
|
165
|
+
bound anywhere in it; `after`: the line added imports go after; `defined`: each name it binds
|
|
166
|
+
at its top level (an import, a class, a function, an assignment), and the line it's first bound
|
|
167
|
+
on; `added`: each name an added import binds, and that import's statement, as `spell` chose them.
|
|
168
|
+
`guarded`: the names other checked files' types and a type checker's hints are written with
|
|
169
|
+
that the module imports (or is to import) under `if TYPE_CHECKING:` alone; `checking`: its own
|
|
170
|
+
such imports, where one goes, and whose types need one (see `Checking`); `postponed`: whether
|
|
171
|
+
it has `from __future__ import annotations`, so none of its annotations is evaluated.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
bound: Mapping[str, str]
|
|
175
|
+
taken: frozenset[str]
|
|
176
|
+
after: int
|
|
177
|
+
defined: Mapping[str, int] = field(default_factory=dict[str, int])
|
|
178
|
+
added: dict[str, str] = field(default_factory=dict[str, str])
|
|
179
|
+
guarded: Mapping[str, Guarded] = field(default_factory=dict[str, Guarded])
|
|
180
|
+
checking: Checking = field(default_factory=Checking)
|
|
181
|
+
postponed: bool = False
|
|
182
|
+
values: frozenset[str] = frozenset() # names it binds as values somewhere (see `imports.taken_names`)
|
|
183
|
+
|
|
184
|
+
def type_checking(self) -> str | None:
|
|
185
|
+
"""Name `typing.TYPE_CHECKING` for a new block, by an import that runs: the module reads it.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
The name, or `None` if every way to write it is taken.
|
|
189
|
+
|
|
190
|
+
"""
|
|
191
|
+
return self.spell("typing.TYPE_CHECKING", runs=True)
|
|
192
|
+
|
|
193
|
+
def spell(self, qualified: str, *, runs: bool = False) -> str | None:
|
|
194
|
+
"""Name `qualified` (`io.BufferedReader`) in this module, adding an import if it has to.
|
|
195
|
+
|
|
196
|
+
Through an import it has (see `named`), for type checking alone too (`checking`, or one
|
|
197
|
+
`guarded` already), else a new `from io import BufferedReader`, else a new `import io`, but
|
|
198
|
+
only binding a name nothing in the module binds. Under `if TYPE_CHECKING:` for a module in
|
|
199
|
+
`checking.lazy`, and not at all for one only some platforms have. With `runs`, only through
|
|
200
|
+
an import that runs: the name is read as the module is imported (`TYPE_CHECKING` itself).
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
The name, or `None` if every way to write it is taken.
|
|
204
|
+
|
|
205
|
+
"""
|
|
206
|
+
found: str | None
|
|
207
|
+
if (found := self.named(qualified) or (None if runs else self._checked(qualified))) is not None:
|
|
208
|
+
return found
|
|
209
|
+
module: str
|
|
210
|
+
name: str
|
|
211
|
+
module, _, name = qualified.rpartition(".")
|
|
212
|
+
statement: str = f"from {module} import {name}"
|
|
213
|
+
top: str = module.partition(_DOT)[0]
|
|
214
|
+
if not runs and top in self.checking.lazy:
|
|
215
|
+
if top in _PLATFORM or module in _PLATFORM:
|
|
216
|
+
return None
|
|
217
|
+
return name if self.guard(name, (module, name), statement) else None
|
|
218
|
+
if self._free(name, statement):
|
|
219
|
+
self.added[name] = statement
|
|
220
|
+
return name
|
|
221
|
+
statement = f"import {module}"
|
|
222
|
+
if _DOT not in module and self._free(module, statement):
|
|
223
|
+
self.added[module] = statement
|
|
224
|
+
return qualified
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
def named(self, qualified: str) -> str | None:
|
|
228
|
+
"""Name `qualified` (`io.BufferedReader`) through an import the module has.
|
|
229
|
+
|
|
230
|
+
`io.BufferedReader` after `import io`, `BufferedReader` after `from io import BufferedReader`.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
The name, or `None` if no import of its names it.
|
|
234
|
+
|
|
235
|
+
"""
|
|
236
|
+
module: str
|
|
237
|
+
name: str
|
|
238
|
+
module, _, name = qualified.rpartition(".")
|
|
239
|
+
bound: str
|
|
240
|
+
origin: str
|
|
241
|
+
for bound, origin in self.bound.items():
|
|
242
|
+
if origin == qualified:
|
|
243
|
+
return bound
|
|
244
|
+
for bound, origin in self.bound.items():
|
|
245
|
+
if origin == module:
|
|
246
|
+
return f"{bound}.{name}"
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
def _checked(self, qualified: str) -> str | None:
|
|
250
|
+
"""Name `qualified` through an import for type checking alone: one it has, or is to have.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
The name, or `None` if none binds it, or the module binds that name to a value too.
|
|
254
|
+
|
|
255
|
+
"""
|
|
256
|
+
origins: dict[str, str] = dict(self.checking.bound)
|
|
257
|
+
bound: str
|
|
258
|
+
each: Guarded
|
|
259
|
+
for bound, each in self.guarded.items():
|
|
260
|
+
origins[bound] = _DOT.join(part for part in each.origin if part)
|
|
261
|
+
return next(
|
|
262
|
+
(bound for bound, origin in origins.items() if origin == qualified and bound not in self.values),
|
|
263
|
+
None,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
def guard(self, name: str, origin: Origin, statement: str) -> bool:
|
|
267
|
+
"""Let annotations be written with `name`, bound to `origin` for type checking alone.
|
|
268
|
+
|
|
269
|
+
By the import `statement`, to add under `if TYPE_CHECKING:`. A name already guarded must be
|
|
270
|
+
so by the same statement; a new import must bind a name nothing in the module binds.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Whether they can.
|
|
274
|
+
|
|
275
|
+
"""
|
|
276
|
+
found: Guarded | None
|
|
277
|
+
if (found := self.guarded.get(name)) is not None:
|
|
278
|
+
return found.statement == statement
|
|
279
|
+
if name in self.added or not self._free(name, statement):
|
|
280
|
+
return False
|
|
281
|
+
# A new mapping: the one it had is the file's `Outside.guarded`, which other files' types read.
|
|
282
|
+
self.guarded = {**self.guarded, name: Guarded(origin, statement)}
|
|
283
|
+
return True
|
|
284
|
+
|
|
285
|
+
def _free(self, name: str, statement: str) -> bool:
|
|
286
|
+
"""Check that `statement` may bind `name`: it already does, or nothing (not a builtin) does.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Whether it may.
|
|
290
|
+
|
|
291
|
+
"""
|
|
292
|
+
if name in self.added:
|
|
293
|
+
return self.added[name] == statement
|
|
294
|
+
return name not in self.taken and name not in _BUILTINS and name not in self.guarded
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class LibraryNames(NamedTuple):
|
|
298
|
+
"""How the module names the library functions `--fix` knows.
|
|
299
|
+
|
|
300
|
+
`typing.cast` (see `casts`), and the standard library's (see `stdlib.origins`). `installed`: the
|
|
301
|
+
installed packages' functions it calls whose arguments decide their type, by the call's name as
|
|
302
|
+
written, each with its signatures (their classes' methods too, as `np.ndarray.astype`), and
|
|
303
|
+
`classes` the installed classes it passes as arguments, which may bind their type variables;
|
|
304
|
+
`parameters`: those methods' classes' type parameters, which a receiver's type binds; `lineage`:
|
|
305
|
+
the installed classes it names, each with where it and its ancestors are defined, which a
|
|
306
|
+
receiver's type is matched by; `aliases`: the public aliases of installed generic classes it
|
|
307
|
+
names, whose methods are their classes' (see `constricter.fix.index.stubs.stubbed`). `starred`: whether
|
|
308
|
+
every Python the module runs on parses an unpacked tuple in a subscript (`tuple[int, *Ts]`,
|
|
309
|
+
3.11's syntax), so an annotation may be written with one. `aliased`: whether `typing` has
|
|
310
|
+
`TypeAlias` on every Python the module runs on (3.10's; yes where that isn't known), so a new
|
|
311
|
+
import may name it.
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
casts: frozenset[str] = frozenset()
|
|
315
|
+
stdlib: Mapping[str, str] = MappingProxyType({})
|
|
316
|
+
plan: ImportPlan | None = None # how to name a type the module doesn't import yet
|
|
317
|
+
installed: Mapping[str, tuple[ReadSignature, ...]] = MappingProxyType({})
|
|
318
|
+
classes: frozenset[str] = frozenset()
|
|
319
|
+
parameters: Mapping[str, tuple[str, ...]] = MappingProxyType({})
|
|
320
|
+
lineage: Mapping[str, tuple[str, ...]] = MappingProxyType({})
|
|
321
|
+
aliases: Mapping[str, Expansion] = MappingProxyType({})
|
|
322
|
+
starred: bool = False
|
|
323
|
+
aliased: bool = True
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class Returned(NamedTuple):
|
|
327
|
+
"""Return types read from unannotated functions' own `return` statements (see `returned`).
|
|
328
|
+
|
|
329
|
+
`calls`: the module's functions', by name (certain); `methods`: its classes' methods', by class
|
|
330
|
+
(guesses: a subclass may override one). `guesses`: for a function (by name) or method (`C.m`)
|
|
331
|
+
whose `return`s are themselves guesses, what they rest on (`FIX_KINDS`), which its calls do too.
|
|
332
|
+
`attributes`: its classes' unannotated instance attributes typed by their every `self.x = value`,
|
|
333
|
+
by class (guesses: a subclass or outside code may assign one too); a guessed value's origins are
|
|
334
|
+
in `guesses` as `C.x`.
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
calls: Mapping[str, str] = MappingProxyType({})
|
|
338
|
+
methods: Mapping[str, Mapping[str, str]] = MappingProxyType({})
|
|
339
|
+
guesses: Mapping[str, frozenset[str]] = MappingProxyType({})
|
|
340
|
+
attributes: Mapping[str, Mapping[str, str]] = MappingProxyType({})
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class Partial(NamedTuple):
|
|
344
|
+
"""Declared returns that have a vague part (`tuple[Row, dict[str, Any]]`, `Any`).
|
|
345
|
+
|
|
346
|
+
One types a call only as far as `vague` allows (see `Limits.vague`); an unpacking takes its
|
|
347
|
+
parts. `calls`: functions', by the call's name as written; `methods`: classes' methods', by the
|
|
348
|
+
class's name as spelled.
|
|
349
|
+
"""
|
|
350
|
+
|
|
351
|
+
# Plain `dict`s, not `MappingProxyType`s: the CLI's worker processes are sent them, pickled.
|
|
352
|
+
calls: Mapping[str, str] = Empty()
|
|
353
|
+
methods: Mapping[str, Mapping[str, str]] = Empty()
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
# A function's parameters declared a builtin container (`names: list[str]`), by name: each one's
|
|
357
|
+
# position (`None`: keyword only), whether a keyword can pass it, and its type as the calling module
|
|
358
|
+
# writes it. What an empty container passed to one is taken as (see `constricter.fix.values.fills`).
|
|
359
|
+
Takers: TypeAlias = Mapping[str, tuple[int | None, bool, str]]
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class Indirect(NamedTuple):
|
|
363
|
+
"""Declared returns that type something other than a plain call.
|
|
364
|
+
|
|
365
|
+
`awaits`: what awaiting a call to each of the module's `async def`s gives (see
|
|
366
|
+
`awaited_returns`); `partial`: those only an unpacking can use (see `Partial`), other checked
|
|
367
|
+
files' too. `tuples`: the named tuples the module names, its own and other checked files', each
|
|
368
|
+
with the tuple unpacking one gives (see `targets.named_tuples`). `unions`: the type aliases of a
|
|
369
|
+
union it names, its own and other checked files', each with the union as its module writes it
|
|
370
|
+
(see `targets.aliased_unions`). `takers`: the functions it calls that declare a parameter a
|
|
371
|
+
builtin container, its own and other checked files', each with those parameters (see `Takers`).
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
awaits: Mapping[str, str] = MappingProxyType({})
|
|
375
|
+
partial: Partial = Partial()
|
|
376
|
+
tuples: Mapping[str, str] = MappingProxyType({})
|
|
377
|
+
unions: Mapping[str, str] = MappingProxyType({})
|
|
378
|
+
takers: Mapping[str, Takers] = MappingProxyType({})
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class ClassSide(NamedTuple):
|
|
382
|
+
"""What each class the module defines offers beyond its instances' own members.
|
|
383
|
+
|
|
384
|
+
On the class itself: `class_attributes`, `class_methods`. From its bases: `lineage`, which base
|
|
385
|
+
an instance takes a method from (see `Lineage`). In its body: `variables`, the plain classes'
|
|
386
|
+
typed by their values, the module's own and those it imports (see `constricter.fix.values.classvars`);
|
|
387
|
+
`plain`: which of its own classes are plain.
|
|
388
|
+
"""
|
|
389
|
+
|
|
390
|
+
attributes: Mapping[str, Mapping[str, str]]
|
|
391
|
+
methods: Mapping[str, Mapping[str, str]]
|
|
392
|
+
lineage: Lineage = Lineage()
|
|
393
|
+
variables: Mapping[str, Mapping[str, str]] = MappingProxyType({})
|
|
394
|
+
plain: frozenset[str] = frozenset()
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
class Limits(NamedTuple):
|
|
398
|
+
"""What the rules let an annotation be, which `--fix` writes no further than.
|
|
399
|
+
|
|
400
|
+
`max_length`: the longest tuple display typed element by element (LVA011's); `vague`: how vague a
|
|
401
|
+
type may be (LVA005's level, see `annotations.vague_fits`). `marks`: the lines a widening marked,
|
|
402
|
+
whose annotations are vaguer by design, each with its mark's columns (see `rules.widened`).
|
|
403
|
+
"""
|
|
404
|
+
|
|
405
|
+
max_length: int = MAX_LENGTH
|
|
406
|
+
vague: int = VAGUE
|
|
407
|
+
marks: Mapping[int, tuple[int, int]] = MappingProxyType({})
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
@dataclass(frozen=True)
|
|
411
|
+
class Known: # pylint: disable=too-many-instance-attributes
|
|
412
|
+
"""What a module declares that `--fix` can infer a value's type from.
|
|
413
|
+
|
|
414
|
+
`calls`: its functions' return types (`returns`, plus other modules', see `project.calls`).
|
|
415
|
+
`factories`: names that build a class or special form rather than an instance of it (see
|
|
416
|
+
`factories`), so a call to one is never guessed to construct one. `classes` and `methods`: each
|
|
417
|
+
class's annotated attributes (see `classes`) and methods' return types (see `method_returns`).
|
|
418
|
+
`indirect`: its declared returns no plain call has (see `Indirect`).
|
|
419
|
+
`class_side`: what `cls.x` and `cls.method()` give in a classmethod, where `cls` is `type[C]`
|
|
420
|
+
(see `ClassSide`). `names`: how it names library functions (see `LibraryNames`). `returned`: what
|
|
421
|
+
its unannotated functions return (see `Returned`).
|
|
422
|
+
"""
|
|
423
|
+
|
|
424
|
+
calls: Mapping[str, str]
|
|
425
|
+
factories: frozenset[str]
|
|
426
|
+
classes: Mapping[str, Mapping[str, str]]
|
|
427
|
+
methods: Mapping[str, Mapping[str, str]]
|
|
428
|
+
indirect: Indirect = field(default_factory=Indirect)
|
|
429
|
+
class_side: "ClassSide" = field(default_factory=lambda: ClassSide({}, {}))
|
|
430
|
+
names: LibraryNames = field(default_factory=LibraryNames)
|
|
431
|
+
limits: Limits = field(default_factory=Limits)
|
|
432
|
+
returned: Returned = field(default_factory=Returned)
|
|
433
|
+
# The decorators that give a function back, as the module spells them (see `rules.decorators`).
|
|
434
|
+
vouched: frozenset[str] = frozenset()
|
|
435
|
+
|
|
436
|
+
def is_builtin(self, name: str) -> bool:
|
|
437
|
+
"""Check that `name` still means the builtin: nothing in the module binds it (a parameter, say).
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
Whether it does; with no import plan (a module not read for one), whether it's a builtin.
|
|
441
|
+
|
|
442
|
+
"""
|
|
443
|
+
return name in _BUILTINS and (self.names.plan is None or name not in self.names.plan.taken)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
class Returns(NamedTuple):
|
|
447
|
+
"""What a module's unannotated functions return (`Returned.calls`), for the files importing them.
|
|
448
|
+
|
|
449
|
+
`calls`: each function's type, by its name (or, imported, as the importing file spells it: `f`,
|
|
450
|
+
`u.f`); `guesses`: for one whose `return`s are guesses, what they rest on (`FIX_KINDS`);
|
|
451
|
+
`names`: what each name their types use that the module imports for type checking alone
|
|
452
|
+
(see `Guarded`) refers to. `methods`: its classes' methods' (`Returned.methods`), by class (as
|
|
453
|
+
the importing file spells it), a guessed one's origins in `guesses` as `C.m`.
|
|
454
|
+
"""
|
|
455
|
+
|
|
456
|
+
# Plain `dict`s, not `MappingProxyType`s: the CLI's worker processes are sent them, pickled.
|
|
457
|
+
calls: Mapping[str, str] = Empty()
|
|
458
|
+
guesses: Mapping[str, frozenset[str]] = Empty()
|
|
459
|
+
names: Mapping[str, Origin] = Empty()
|
|
460
|
+
methods: Mapping[str, Mapping[str, str]] = Empty()
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
class Offered(NamedTuple):
|
|
464
|
+
"""What a type checker's hint would write if its editor accepted it (the hint's own edits).
|
|
465
|
+
|
|
466
|
+
`text`: the annotation as it would insert it, which may spell a class through a module the file
|
|
467
|
+
imports (`collections.Counter[str]`, where the hint shows `Counter[str]`); `imports`: the
|
|
468
|
+
imports it would add for the names in it, each a statement binding one name
|
|
469
|
+
(`from shapes import Shape`, `import shapes`).
|
|
470
|
+
"""
|
|
471
|
+
|
|
472
|
+
text: str
|
|
473
|
+
imports: tuple[str, ...] = ()
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
class Hints(NamedTuple):
|
|
477
|
+
"""A type checker's inlay hints for one file (`--infer-with`): which checker, and each type.
|
|
478
|
+
|
|
479
|
+
Or a traced run's types for it (`--infer-from`), of `kind` `traced`: what its fixes rest on.
|
|
480
|
+
|
|
481
|
+
Each hint's type is its text as the checker printed it (`int`, `list[str]`), by where the name
|
|
482
|
+
it types ends: its line (from 1) and UTF-8 byte column, as `ast`'s `end_col_offset`.
|
|
483
|
+
`offered`: what each hint that has edits would write (see `Offered`), by the same place.
|
|
484
|
+
"""
|
|
485
|
+
|
|
486
|
+
checker: str = ""
|
|
487
|
+
# Plain `dict`s, not `MappingProxyType`s: the CLI's worker processes are sent them, pickled.
|
|
488
|
+
types: Mapping[tuple[int, int], str] = Empty()
|
|
489
|
+
offered: Mapping[tuple[int, int], Offered] = Empty()
|
|
490
|
+
kind: str = "checker"
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
# An argument's type, and what it rests on if it's a guess (`FIX_KINDS`; none: it's certain).
|
|
494
|
+
Passed: TypeAlias = tuple[str, frozenset[str]]
|
|
495
|
+
# A module's functions' parameters every call passes one type: each one's, by name, by function.
|
|
496
|
+
Seeds: TypeAlias = Mapping[str, Mapping[str, Passed]]
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
class Call(NamedTuple):
|
|
500
|
+
"""One call to a checked file's function: what `--fix` knows each argument's type to be (`None`: unknown).
|
|
501
|
+
|
|
502
|
+
`unpacked`: whether it passes `*args` or `**kwargs`, whose arguments can't be matched.
|
|
503
|
+
"""
|
|
504
|
+
|
|
505
|
+
positional: tuple[Passed | None, ...] = ()
|
|
506
|
+
keywords: tuple[tuple[str, Passed | None], ...] = ()
|
|
507
|
+
unpacked: bool = False
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
Callee: TypeAlias = tuple[str, str] # a checked file's function: its module and name
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
class Observed(NamedTuple):
|
|
514
|
+
"""What a module does with checked files' functions whose parameters aren't all annotated.
|
|
515
|
+
|
|
516
|
+
`calls`: each call to one, by the function; `escaped`: those it uses other than by calling them
|
|
517
|
+
(a callback, a stored reference), whose callers can't be known.
|
|
518
|
+
"""
|
|
519
|
+
|
|
520
|
+
# Plain `dict`s and tuples, not `MappingProxyType`s: the CLI's worker processes send them back, pickled.
|
|
521
|
+
calls: Mapping[Callee, tuple[Call, ...]] = Empty()
|
|
522
|
+
escaped: frozenset[Callee] = frozenset()
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def pickled(frozen: "DataclassInstance") -> dict[str, object]:
|
|
526
|
+
"""Give a frozen dataclass's fields, to pickle it by: a class mypyc compiled has no other.
|
|
527
|
+
|
|
528
|
+
Returns:
|
|
529
|
+
Each field's value, by its name.
|
|
530
|
+
|
|
531
|
+
"""
|
|
532
|
+
return {each.name: getattr(frozen, each.name) for each in fields(frozen)}
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def unpickled(frozen: "DataclassInstance", state: Mapping[str, object]) -> None:
|
|
536
|
+
"""Set a frozen dataclass's fields from what `pickled` gave, past the class's own refusal."""
|
|
537
|
+
name: str
|
|
538
|
+
for name in state:
|
|
539
|
+
# Past the class's own `__setattr__`, which refuses.
|
|
540
|
+
object.__setattr__(frozen, name, state[name]) # ruff: ignore[unnecessary-dunder-call]
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
544
|
+
class Outside: # pylint: disable=too-many-instance-attributes
|
|
545
|
+
"""What the CLI knows of a file from outside it, for `--fix`.
|
|
546
|
+
|
|
547
|
+
`calls`: the return types of functions other checked files define, `returned` those of their
|
|
548
|
+
unannotated functions (see `Returns`), and `classes` their classes' attributes and methods'
|
|
549
|
+
returns, as the file spells them (see `crossed.imported`); `hints`, a
|
|
550
|
+
type checker's types for what `--fix` can't type itself (`--infer-with`); `type_vars`, the names
|
|
551
|
+
it imports that are type variables where they're defined (see `project.type_vars`), which a
|
|
552
|
+
type its own functions declare can't be written with outside them; `guarded`, the names those
|
|
553
|
+
types are written with that it can use in an annotation alone (see `Guarded`); `generics`, the
|
|
554
|
+
generic classes it imports from them, which a fix mustn't write bare. `callees`: the functions of
|
|
555
|
+
checked files it may call whose parameters aren't all annotated, as it spells them (`f`, `u.f`);
|
|
556
|
+
`parameters`: for its own such functions, each parameter every call passes one type (see
|
|
557
|
+
`constricter.fix.index.callers`). `plain`: which of its own classes are plain, as the index of checked
|
|
558
|
+
files settles it (`None`: as the file alone sees, see `constricter.fix.values.classvars`); `members`:
|
|
559
|
+
the variables of the plain classes it imports from them, typed by their values, as it spells
|
|
560
|
+
each class. `same`: each group of ways it spells one class or alias another module defines
|
|
561
|
+
(`CoreSchema`, `core_schema.CoreSchema`; see `linked.same`). `partial`: the returns of other
|
|
562
|
+
checked files' functions and methods that only an unpacking can use (see `Partial`). `tuples`:
|
|
563
|
+
the named tuples it imports from them, as it spells each (see `Indirect.tuples`), and `unions`
|
|
564
|
+
their type aliases of a union (see `Indirect.unions`). `untyped`: the functions it calls of
|
|
565
|
+
theirs that declare no return, as it spells each call (see `linked.untyped`).
|
|
566
|
+
"""
|
|
567
|
+
|
|
568
|
+
calls: Mapping[str, str] = field(default_factory=dict[str, str])
|
|
569
|
+
classes: Classes | None = None
|
|
570
|
+
hints: tuple[Hints, ...] = () # each checker's, in the order they were named
|
|
571
|
+
type_vars: frozenset[str] = frozenset()
|
|
572
|
+
returned: Returns = field(default_factory=Returns)
|
|
573
|
+
guarded: Mapping[str, Guarded] = field(default_factory=dict[str, Guarded])
|
|
574
|
+
generics: frozenset[str] = frozenset() # other checked files' generic classes, as it spells them
|
|
575
|
+
callees: Mapping[str, Callee] = field(default_factory=dict[str, Callee])
|
|
576
|
+
# See `Seeds`
|
|
577
|
+
parameters: Mapping[str, Mapping[str, Passed]] = field(
|
|
578
|
+
default_factory=dict[str, Mapping[str, Passed]],
|
|
579
|
+
)
|
|
580
|
+
# See `LibraryNames.installed`
|
|
581
|
+
overloaded: Mapping[str, tuple[ReadSignature, ...]] = field(
|
|
582
|
+
default_factory=dict[str, tuple[ReadSignature, ...]],
|
|
583
|
+
)
|
|
584
|
+
installed_classes: frozenset[str] = frozenset() # see `LibraryNames.classes`
|
|
585
|
+
# See `LibraryNames.parameters`
|
|
586
|
+
installed_parameters: Mapping[str, tuple[str, ...]] = field(default_factory=dict[str, tuple[str, ...]])
|
|
587
|
+
# See `LibraryNames.lineage`
|
|
588
|
+
installed_lineage: Mapping[str, tuple[str, ...]] = field(default_factory=dict[str, tuple[str, ...]])
|
|
589
|
+
# See `LibraryNames.aliases`
|
|
590
|
+
installed_aliases: Mapping[str, Expansion] = field(
|
|
591
|
+
default_factory=dict[str, Expansion],
|
|
592
|
+
)
|
|
593
|
+
# The classes it imports under `if TYPE_CHECKING:` alone, which an annotation can name (see `offers.own`).
|
|
594
|
+
checking: Mapping[str, Guarded] = field(default_factory=dict[str, Guarded])
|
|
595
|
+
plain: frozenset[str] | None = None
|
|
596
|
+
members: Mapping[str, Mapping[str, str]] = field(default_factory=dict[str, Mapping[str, str]])
|
|
597
|
+
same: tuple[frozenset[str], ...] = ()
|
|
598
|
+
partial: Partial = field(default_factory=Partial)
|
|
599
|
+
tuples: Mapping[str, str] = field(default_factory=dict[str, str])
|
|
600
|
+
# The pytest fixtures its tests can take: each one's value's type (see `constricter.fix.index.fixtures`).
|
|
601
|
+
fixtures: Mapping[str, Passed] = field(default_factory=dict[str, Passed])
|
|
602
|
+
# Its classes' bases other checked files define: where each one's own end (see `Lineage.beyond`).
|
|
603
|
+
beyond: Mapping[str, Beyond] = field(default_factory=dict[str, Beyond])
|
|
604
|
+
# What awaiting a call of each `async def` it imports from them gives, as it spells the call.
|
|
605
|
+
awaits: Mapping[str, str] = field(default_factory=dict[str, str])
|
|
606
|
+
# The names it imports that another checked module binds by assignment (see `linked.values`).
|
|
607
|
+
values: frozenset[str] = frozenset()
|
|
608
|
+
unions: Mapping[str, str] = field(default_factory=dict[str, str])
|
|
609
|
+
untyped: frozenset[str] = frozenset()
|
|
610
|
+
# The attributes its classes take bound to an empty container from their bases (see `beyond.emptied`).
|
|
611
|
+
emptied: Mapping[str, Mapping[str, str]] = field(default_factory=dict[str, Mapping[str, str]])
|
|
612
|
+
# Their functions it calls that declare a parameter a builtin container (see `Indirect.takers`).
|
|
613
|
+
takers: Mapping[str, Takers] = field(default_factory=dict[str, Takers])
|
|
614
|
+
# What `with` gives of each call it makes to their `@contextmanager` functions (see `index.managed`).
|
|
615
|
+
managers: Mapping[str, Passed] = field(default_factory=dict[str, Passed])
|
|
616
|
+
# The decorators it uses that give a function back, by the index (see `index.decorated.vouched`).
|
|
617
|
+
vouched: frozenset[str] = frozenset()
|
|
618
|
+
|
|
619
|
+
@override
|
|
620
|
+
def __getstate__(self) -> dict[str, object]:
|
|
621
|
+
"""Pickle it by its fields (see `pickled`).
|
|
622
|
+
|
|
623
|
+
Returns:
|
|
624
|
+
Them.
|
|
625
|
+
|
|
626
|
+
"""
|
|
627
|
+
return pickled(self)
|
|
628
|
+
|
|
629
|
+
def __setstate__(self, state: dict[str, object]) -> None:
|
|
630
|
+
"""Unpickle it (see `unpickled`)."""
|
|
631
|
+
unpickled(self, state)
|
|
632
|
+
|
|
633
|
+
def usable(self, taken: frozenset[str], present: frozenset[str]) -> "Outside":
|
|
634
|
+
"""Drop what other files offer whose type needs a name imported that the module binds already.
|
|
635
|
+
|
|
636
|
+
That's a name to import under `if TYPE_CHECKING:` (see `Guarded`) that the module binds anywhere
|
|
637
|
+
else, a function's local or parameter included: the import would shadow it, or it the import.
|
|
638
|
+
A checked file's overloaded function is dropped whole, if any of its signatures returns such a
|
|
639
|
+
type: which one a call takes isn't known here.
|
|
640
|
+
`present`: those it imports so already (see `imports.present`), which `--fix` wrote since the
|
|
641
|
+
files were indexed: each is one the file has.
|
|
642
|
+
|
|
643
|
+
Returns:
|
|
644
|
+
What's left.
|
|
645
|
+
|
|
646
|
+
"""
|
|
647
|
+
guarded: dict[str, Guarded] = {
|
|
648
|
+
name: Guarded(found.origin, None) if name in present else found
|
|
649
|
+
for name, found in self.guarded.items()
|
|
650
|
+
}
|
|
651
|
+
clashing: frozenset[str] = frozenset(
|
|
652
|
+
name for name, found in guarded.items() if found.statement is not None and name in taken
|
|
653
|
+
)
|
|
654
|
+
if not clashing and guarded == self.guarded:
|
|
655
|
+
return self
|
|
656
|
+
members: Classes | None = self.classes
|
|
657
|
+
return Outside(
|
|
658
|
+
calls=free_of(self.calls, clashing),
|
|
659
|
+
classes=None
|
|
660
|
+
if members is None
|
|
661
|
+
else Classes(free_of_all(members.attributes, clashing), free_of_all(members.methods, clashing)),
|
|
662
|
+
hints=self.hints,
|
|
663
|
+
type_vars=self.type_vars,
|
|
664
|
+
returned=Returns(
|
|
665
|
+
free_of(self.returned.calls, clashing),
|
|
666
|
+
self.returned.guesses,
|
|
667
|
+
self.returned.names,
|
|
668
|
+
free_of_all(self.returned.methods, clashing),
|
|
669
|
+
),
|
|
670
|
+
guarded={name: found for name, found in guarded.items() if name not in clashing},
|
|
671
|
+
generics=self.generics,
|
|
672
|
+
callees=self.callees,
|
|
673
|
+
parameters=self.parameters,
|
|
674
|
+
overloaded={
|
|
675
|
+
callee: signatures
|
|
676
|
+
for callee, signatures in self.overloaded.items()
|
|
677
|
+
if not any(clashing.intersection(_NAME.findall(each.returns or "")) for each in signatures)
|
|
678
|
+
},
|
|
679
|
+
installed_classes=self.installed_classes,
|
|
680
|
+
installed_parameters=self.installed_parameters,
|
|
681
|
+
installed_lineage=self.installed_lineage,
|
|
682
|
+
installed_aliases=self.installed_aliases,
|
|
683
|
+
checking=self.checking,
|
|
684
|
+
plain=self.plain,
|
|
685
|
+
members=self.members,
|
|
686
|
+
same=self.same,
|
|
687
|
+
partial=Partial(
|
|
688
|
+
free_of(self.partial.calls, clashing),
|
|
689
|
+
free_of_all(self.partial.methods, clashing),
|
|
690
|
+
),
|
|
691
|
+
tuples=free_of(self.tuples, clashing),
|
|
692
|
+
fixtures={name: typed for name, typed in self.fixtures.items() if not roots(typed[0]) & clashing},
|
|
693
|
+
beyond=self.beyond,
|
|
694
|
+
awaits=free_of(self.awaits, clashing),
|
|
695
|
+
values=self.values,
|
|
696
|
+
unions=self.unions,
|
|
697
|
+
untyped=self.untyped,
|
|
698
|
+
managers={name: typed for name, typed in self.managers.items() if not roots(typed[0]) & clashing},
|
|
699
|
+
emptied=self.emptied,
|
|
700
|
+
takers={
|
|
701
|
+
name: {param: taken for param, taken in takers.items() if not roots(taken[2]) & clashing}
|
|
702
|
+
for name, takers in self.takers.items()
|
|
703
|
+
},
|
|
704
|
+
vouched=self.vouched,
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
class Inference(NamedTuple):
|
|
709
|
+
"""An annotation `--fix` would add, how the value decided it (`--show-fixes`), and by which means.
|
|
710
|
+
|
|
711
|
+
`kinds` are the `FIX_KINDS` ids of every mechanism that decided it, parts included (`[1, 2]`
|
|
712
|
+
is a `container` of `literal`s), for `fix-select` and `fix-ignore`. `reads`: the names,
|
|
713
|
+
attributes and subscripts (as source text) whose own types it takes as they are (`deque([x])`'s
|
|
714
|
+
`x`), which a type checker sees narrowed where the function tests them.
|
|
715
|
+
"""
|
|
716
|
+
|
|
717
|
+
annotation: str
|
|
718
|
+
reason: str
|
|
719
|
+
kinds: frozenset[str] = frozenset()
|
|
720
|
+
reads: tuple[str, ...] = ()
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def kinds_of(*parts: Inference | None, kind: str) -> frozenset[str]:
|
|
724
|
+
"""Collect `kind` and the kinds of each part an inference was built from.
|
|
725
|
+
|
|
726
|
+
Returns:
|
|
727
|
+
Them all.
|
|
728
|
+
|
|
729
|
+
"""
|
|
730
|
+
return frozenset({kind}).union(*(part.kinds for part in parts if part is not None))
|