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
|
Binary file
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""The signatures `--fix` matches a call against: the standard library's tables', and installed packages'.
|
|
3
|
+
|
|
4
|
+
What each parameter takes (`Accepts`), and a signature as the tables hold it (`Signature`) and as
|
|
5
|
+
it's matched (`ReadSignature`); see `constricter.fix.libraries.overloads`, which matches them,
|
|
6
|
+
`constricter.fix.libraries.stdlib`, whose tables hold them, and `constricter.fix.index.stubs.stubbed`,
|
|
7
|
+
which reads an installed package's.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Final, NamedTuple, NotRequired, Required, TypeAlias, TypedDict
|
|
11
|
+
|
|
12
|
+
# `Accepts`' keys for an installed package's parameter (see `constricter.fix.index.stubs.signatures`): a
|
|
13
|
+
# class passed as the argument, and a builtin container.
|
|
14
|
+
# Before a path, an `async def`'s entry for `overloads.chosen`; before a method's name in
|
|
15
|
+
# `method_overloads`, an `async def` method's: its signatures' returns are what awaiting a call gives.
|
|
16
|
+
AWAIT: Final = "await "
|
|
17
|
+
CLASS_VERDICT: Final = "k"
|
|
18
|
+
CLASS_BINDS: Final = "kv"
|
|
19
|
+
CLASS_BOUNDED: Final = "kb"
|
|
20
|
+
CLASS_TUPLE: Final = "kt"
|
|
21
|
+
CONTAINER_VERDICTS: Final = "b"
|
|
22
|
+
ELEMENT_VERDICTS: Final = "be"
|
|
23
|
+
CONTAINER_BINDS: Final = "bc"
|
|
24
|
+
OWN: Final = "own"
|
|
25
|
+
OWN_ELEMENTS: Final = "own_e"
|
|
26
|
+
FUTURE_BINDS: Final = "f"
|
|
27
|
+
|
|
28
|
+
Constant: TypeAlias = bool | int | float | complex | str | bytes | None # a literal's value
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Accepts(TypedDict, total=False):
|
|
32
|
+
"""Which argument types a parameter takes (see `constricter.fix.libraries.overloads`).
|
|
33
|
+
|
|
34
|
+
`v`: a verdict (`y`, `n`, `?`) per `overloads.SCALARS` type, for an argument that isn't a
|
|
35
|
+
literal; `c`: for a literal not among `lit` (its `Literal[...]` values), where that differs;
|
|
36
|
+
`var`: the type variable the parameter is, and its type, for an argument of each type; or just
|
|
37
|
+
its name, where each binds it to its own type (a `str` literal's `str`). `t`: the type variable
|
|
38
|
+
the parameter is, unbounded, so any argument binds it to its own type (`copy.copy(x)`). `e`, for
|
|
39
|
+
a parameter that is a generic class of one type variable (`Iterable[_T]`): that variable, which
|
|
40
|
+
an argument of a builtin container in `of` (`list[str]`) binds to its type argument at that index;
|
|
41
|
+
`ei`, for one of a generic class of another (`Iterable[Iterable[_S]]`): the container that type
|
|
42
|
+
argument must be in turn, and the index of its own that binds the variable (`list[list[int]]`).
|
|
43
|
+
`r`: the type variable a callable parameter returns (`Callable[..., _T]`), which a function
|
|
44
|
+
argument binds to its declared return (`functools.partial(helper, 1)`). `w`: the type variable
|
|
45
|
+
an awaitable parameter gives awaited (`Coroutine[Any, Any, _T]`), which a coroutine's call binds
|
|
46
|
+
to what awaiting it gives (`asyncio.run(main())`). `k`: for an installed
|
|
47
|
+
package's parameter, a verdict for a class passed as the argument (`dtype=np.float64`), `kv`
|
|
48
|
+
the type variable it binds to that class (`type[_T]`'s `_T`), `kb` whether that variable
|
|
49
|
+
is bounded: a builtin class may be outside its bound, and another signature's to take; and `kt`
|
|
50
|
+
whether a tuple of classes is taken too (`type[_E] | tuple[type[_E], ...]`), which binds the
|
|
51
|
+
variable to their union. `b`: a
|
|
52
|
+
verdict per builtin container argument (`tuple`), and `be`, where the parameter says what its
|
|
53
|
+
elements must be, a verdict per `SCALARS` type for them; `bc`: the bounded type variable a
|
|
54
|
+
container argument it takes binds. `f`: the type variable the parameter is, bounded by a future
|
|
55
|
+
(`asyncio.ensure_future`'s `_FT`), which a future or a task binds to its type, and no
|
|
56
|
+
coroutine's call does.
|
|
57
|
+
`own`: for a checked file's parameter that takes nothing but checked files' classes, their paths,
|
|
58
|
+
which an argument's class is matched against by its lineage; `own_e`: those a builtin sequence's
|
|
59
|
+
or set's elements must be, for one that takes iterables of them (see `index.own_types`).
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
v: Required[str]
|
|
63
|
+
c: str
|
|
64
|
+
lit: list[Constant]
|
|
65
|
+
var: str | dict[str, list[str]]
|
|
66
|
+
t: str
|
|
67
|
+
e: str
|
|
68
|
+
of: dict[str, int]
|
|
69
|
+
ei: dict[str, int]
|
|
70
|
+
r: str
|
|
71
|
+
w: str
|
|
72
|
+
k: str
|
|
73
|
+
kv: str
|
|
74
|
+
kb: bool
|
|
75
|
+
kt: bool
|
|
76
|
+
b: dict[str, str]
|
|
77
|
+
be: dict[str, str]
|
|
78
|
+
bc: str
|
|
79
|
+
f: str
|
|
80
|
+
own: list[str]
|
|
81
|
+
own_e: list[str]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# A parameter: its name, kind (`p` positional, `e` either, `k` keyword, `a` `*args`, `w` `**kwargs`),
|
|
85
|
+
# whether it has a default, and what it takes (`None`: whatever every signature takes there). The
|
|
86
|
+
# tables write one every signature has alike as `"name kind"`, `=` after it if it has a default.
|
|
87
|
+
Parameter: TypeAlias = tuple[str, str, bool, Accepts | None]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Signature(TypedDict):
|
|
91
|
+
"""One signature of a function whose arguments decide its type: its parameters, and its return.
|
|
92
|
+
|
|
93
|
+
The return is a template (see `constricter.fix.libraries.overloads`), or `None` if `--fix` can't write it.
|
|
94
|
+
`self`: for a generic class's method declaring its instance's type (`self: Pattern[str]`), the
|
|
95
|
+
type arguments that instance must have. `takes`: for an operator's method (`__add__`), its one
|
|
96
|
+
operand's type as a template, or `None` if `--fix` can't write it.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
params: list[Parameter | str]
|
|
100
|
+
returns: str | None
|
|
101
|
+
self: NotRequired[list[str]]
|
|
102
|
+
takes: NotRequired[str | None]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
Variant: TypeAlias = list[Signature] # one configuration's signatures, in order
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ReadSignature(NamedTuple):
|
|
109
|
+
"""One signature, read: its parameters in full, its return template, and its `self`'s type arguments."""
|
|
110
|
+
|
|
111
|
+
params: tuple[Parameter, ...]
|
|
112
|
+
returns: str | None
|
|
113
|
+
instance: list[str] | None
|
|
114
|
+
# An installed class's method declaring its `self`: the type its receiver must have, as a pattern
|
|
115
|
+
# (classes by where they're defined, `typing.Any` for anything, type variables bare), and those
|
|
116
|
+
# variables' bounds, likewise (see `constricter.fix.index.stubs.signatures`).
|
|
117
|
+
receiver: str | None = None
|
|
118
|
+
bounds: tuple[tuple[str, str], ...] = ()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class Expansion(NamedTuple):
|
|
122
|
+
"""A public alias of an installed generic class (`npt.NDArray`), as a receiver's type is written.
|
|
123
|
+
|
|
124
|
+
`params`: its own type parameters, renamed apart from any a method names (`_alias0`), which the
|
|
125
|
+
receiver's type arguments bind; `receiver`: what it stands for, as a pattern naming them (see
|
|
126
|
+
`ReadSignature.receiver`); `templates`: the class's type parameters, each with a template naming
|
|
127
|
+
them (`np.dtype[_alias0]`), where one can be written.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
params: tuple[str, ...]
|
|
131
|
+
receiver: str
|
|
132
|
+
templates: tuple[tuple[str, str], ...]
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Cross-module `--fix` for `await` of a call to another checked file's `async def`.
|
|
3
|
+
|
|
4
|
+
Found through the calling file's imports and the re-exports between them (`await load(url)`,
|
|
5
|
+
`await client.load(url)` after `import pkg.client as client`); its declared return types the
|
|
6
|
+
awaited call where the file can write it (see `project.portable`), or else its `return`s do, once
|
|
7
|
+
its file is checked (see `constricter.fix.values.returned`). A method's is its class's (see
|
|
8
|
+
`rules.tables`).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from constricter.fix.core.known import Guarded, Returns
|
|
14
|
+
from constricter.fix.core.signatures import AWAIT
|
|
15
|
+
from constricter.fix.index import project, sides
|
|
16
|
+
from constricter.fix.index.modules import SUFFIX, Index, Module, module_name
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def calls(catalog: Index, path: Path, guarded: dict[str, Guarded]) -> dict[str, str]:
|
|
20
|
+
"""Type what awaiting the calls the file at `path` makes to other checked files' `async def`s gives.
|
|
21
|
+
|
|
22
|
+
`guarded`: the names its imported types need imported for type checking (see `Guarded`), which
|
|
23
|
+
these types' are added to.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Each call's name as written (`load`, `client.load`), mapped to its type; none for a file
|
|
27
|
+
`catalog` doesn't have.
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
found: dict[str, str] = {}
|
|
31
|
+
target: Module | None
|
|
32
|
+
if path.suffix != SUFFIX or (target := catalog.modules.get(module_name(path))) is None:
|
|
33
|
+
return found
|
|
34
|
+
spelled: str
|
|
35
|
+
defined: tuple[Module, str]
|
|
36
|
+
for spelled, defined in sides.called(catalog.modules, target, _awaitable):
|
|
37
|
+
if defined[1] in defined[0].awaits:
|
|
38
|
+
declared: dict[str, str] = {spelled: defined[0].awaits[defined[1]]}
|
|
39
|
+
found.update(project.portable(catalog.modules, (target, defined), spelled, declared, guarded))
|
|
40
|
+
return found
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def returned(catalog: Index, path: Path, guarded: dict[str, Guarded], found: Returns) -> Returns:
|
|
44
|
+
"""Add what awaiting the file's calls to other checked files' undeclared `async def`s gives to `found`.
|
|
45
|
+
|
|
46
|
+
By their `return`s, for modules already checked, each under `AWAIT` before the call's name as
|
|
47
|
+
written, with what it rests on if it's a guess.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
`found`, with them; `found` itself for a file `catalog` doesn't have, or one that calls none.
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
awaited: dict[str, str] = {}
|
|
54
|
+
guesses: dict[str, frozenset[str]] = {}
|
|
55
|
+
target: Module | None
|
|
56
|
+
if path.suffix != SUFFIX or (target := catalog.modules.get(module_name(path))) is None:
|
|
57
|
+
return found
|
|
58
|
+
spelled: str
|
|
59
|
+
defined: tuple[Module, str]
|
|
60
|
+
for spelled, defined in sides.called(catalog.modules, target, _awaitable):
|
|
61
|
+
key: str
|
|
62
|
+
if (key := f"{AWAIT}{defined[1]}") not in defined[0].returned.calls:
|
|
63
|
+
continue
|
|
64
|
+
typed: dict[str, str] = {spelled: defined[0].returned.calls[key]}
|
|
65
|
+
kept: dict[str, str]
|
|
66
|
+
if kept := project.portable(catalog.modules, (target, defined), spelled, typed, guarded):
|
|
67
|
+
awaited[f"{AWAIT}{spelled}"] = kept[spelled]
|
|
68
|
+
if key in defined[0].returned.guesses:
|
|
69
|
+
guesses[f"{AWAIT}{spelled}"] = defined[0].returned.guesses[key]
|
|
70
|
+
if not awaited:
|
|
71
|
+
return found
|
|
72
|
+
return found._replace(calls={**found.calls, **awaited}, guesses={**found.guesses, **guesses})
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def needs(catalog: Index, module: Module) -> set[str]:
|
|
76
|
+
"""Find the other modules whose `async def`s `module` calls that only their `return`s could type.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Their names: checked before it, what awaiting each gives is known.
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
return {
|
|
83
|
+
defined[0].name
|
|
84
|
+
for _, defined in sides.called(catalog.modules, module, _awaitable)
|
|
85
|
+
if defined[1] in defined[0].unawaited
|
|
86
|
+
} - {module.name}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _awaitable(module: Module, name: str) -> bool: # whether it defines an `async def` of the name
|
|
90
|
+
return name in module.awaits or name in module.unawaited
|
|
Binary file
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""The standard-library class another checked file's class inherits from, out of a file's sight.
|
|
3
|
+
|
|
4
|
+
A file's class under another file's (`django.test.TestCase`) can't see what that one inherits from:
|
|
5
|
+
the index can. `library_bases` follows each of a file's classes' bases through the checked files
|
|
6
|
+
and the installed packages, to the standard-library class its lines of bases end at
|
|
7
|
+
(`unittest.TestCase`), with the names the classes on the way bind: any other member is the
|
|
8
|
+
library class's, as `constricter.fix.core.inherited.Lineage.definer` then says. A package that
|
|
9
|
+
declares no types is read for its classes' bases alone (`installed.unseen`). Two lines each ending
|
|
10
|
+
at a library class give both, in order, where they share no ancestor.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from collections.abc import Mapping, Sequence
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import TYPE_CHECKING, Final
|
|
16
|
+
|
|
17
|
+
from constricter.fix.core.inherited import SEVERAL, Beyond
|
|
18
|
+
from constricter.fix.index import plain
|
|
19
|
+
from constricter.fix.index.modules import SUFFIX, Index, Module, module_name, takes
|
|
20
|
+
from constricter.fix.index.stubs import installed
|
|
21
|
+
from constricter.fix.libraries import stdlib
|
|
22
|
+
from constricter.fix.values import classvars
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from constricter.fix.index.stubs.declared import Class
|
|
26
|
+
|
|
27
|
+
_DOT: Final = "."
|
|
28
|
+
_OBJECT: Final = "object"
|
|
29
|
+
_DEPTH: Final = 8 # how many classes a line of bases is followed through
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def library_bases(catalog: Index, path: Path) -> dict[str, Beyond]:
|
|
33
|
+
"""Find where the line of each base of the file at `path`'s classes ends, for one another module defines.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Each such base, as the file writes it, with the standard-library class its line of single
|
|
37
|
+
bases ends at (one the tables hold whole; `""` for a line that ends at none, a mixin's) and
|
|
38
|
+
the names bound on the way; none for a base whose line has a class of several bases or one
|
|
39
|
+
out of sight, nor for a file `catalog` doesn't have.
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
target: Module | None
|
|
43
|
+
if path.suffix != SUFFIX or (target := catalog.modules.get(module_name(path))) is None:
|
|
44
|
+
return {}
|
|
45
|
+
written: set[str] = {base for bases in target.bases.values() for base in bases} - {classvars.SPECIAL}
|
|
46
|
+
ends: dict[str, Beyond | None] = {
|
|
47
|
+
base: _end(catalog.modules, plain.resolved(catalog.modules, target, base))
|
|
48
|
+
for base in sorted(written)
|
|
49
|
+
if base not in target.bases
|
|
50
|
+
}
|
|
51
|
+
return {base: end for base, end in ends.items() if end is not None}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _end(modules: Mapping[str, Module], defined: str) -> Beyond | None:
|
|
55
|
+
"""Follow a class another module defines (`defined`: its dotted path) up its bases.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
The standard-library class it ends at (`""`: none, a class with no base), and the names the
|
|
59
|
+
classes on the way bind; `None` if `defined` is no indexed module's class, or its line
|
|
60
|
+
doesn't end.
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
found: Beyond | None = _line(modules, defined, _DEPTH)
|
|
64
|
+
return None if found is None or (found[0] and not found[1]) else found
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _line(modules: Mapping[str, Module], defined: str, depth: int) -> Beyond | None:
|
|
68
|
+
"""Follow a class's bases to where they end: at a library class, any mixins beside it.
|
|
69
|
+
|
|
70
|
+
A class of several bases ends where those that reach a library class do, the others (each
|
|
71
|
+
ending at none) binding what they bind before them. An installed package that declares no
|
|
72
|
+
types is read for its classes' bases (see `installed.unseen`).
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
The standard-library class (the tables hold whole; `""`: none; several, `SEVERAL` between
|
|
76
|
+
them) and the names bound on the way; `None` for a base out of sight, two lines reaching
|
|
77
|
+
library classes that share an ancestor, or one too deep.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
name: str = defined.rpartition(_DOT)[2]
|
|
81
|
+
module: Module | None = modules.get(defined.rpartition(_DOT)[0])
|
|
82
|
+
library: str | None = stdlib.held_whole(defined)
|
|
83
|
+
if module is None and library is None:
|
|
84
|
+
module = installed.unseen(defined.rpartition(_DOT)[0])
|
|
85
|
+
bases: list[str] | None = None if module is None else _bases(module, name)
|
|
86
|
+
if module is not None and bases is None and depth and takes(module, name): # a re-export: followed
|
|
87
|
+
onward: tuple[str, str | None] = module.names[name]
|
|
88
|
+
return _line(modules, _DOT.join(part for part in onward if part), depth - 1)
|
|
89
|
+
if module is None or bases is None:
|
|
90
|
+
return None if library is None else (library, frozenset())
|
|
91
|
+
hidden: frozenset[str] = module.bound[name] | {name}
|
|
92
|
+
ends: list[Beyond | None] = [
|
|
93
|
+
_line(
|
|
94
|
+
modules,
|
|
95
|
+
f"{module.name}{_DOT}{base}" if base in module.bound else plain.resolved(modules, module, base),
|
|
96
|
+
depth - 1,
|
|
97
|
+
)
|
|
98
|
+
if depth
|
|
99
|
+
else None
|
|
100
|
+
for base in bases
|
|
101
|
+
]
|
|
102
|
+
reached: list[str] = [end[0] for end in ends if end is not None and end[0]]
|
|
103
|
+
lines: list[frozenset[str]] = [stdlib.lines(path) for each in reached for path in each.split(SEVERAL)]
|
|
104
|
+
if None in ends or len(frozenset[str]().union(*lines)) != sum(len(line) for line in lines):
|
|
105
|
+
return None
|
|
106
|
+
return (SEVERAL.join(reached), hidden.union(*(end[1] for end in ends if end is not None)))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _bases(module: Module, name: str) -> list[str] | None:
|
|
110
|
+
"""List the bases of a class `module` (a checked file's, or an installed package's) defines once.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Them as written, `object` left out; `None` if it defines no such class.
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
if name not in module.bound:
|
|
117
|
+
return None
|
|
118
|
+
declared: Class | None = None if module.declared is None else module.declared.classes.get(name)
|
|
119
|
+
written: Sequence[str] = module.bases.get(name, ()) if declared is None else declared.bases
|
|
120
|
+
return [base for base in written if base not in {classvars.SPECIAL, _OBJECT}]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def emptied(catalog: Index, path: Path) -> dict[str, Mapping[str, str]]:
|
|
124
|
+
"""Find the attributes the file at `path`'s classes take bound to an empty container from another file's.
|
|
125
|
+
|
|
126
|
+
A base another checked file defines, which binds one so and does no more with it (see
|
|
127
|
+
`Module.emptied`): what the file's own classes' methods add to it types it for them.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Each such class's attributes' kinds, by its name; none for a file `catalog` doesn't have.
|
|
131
|
+
|
|
132
|
+
"""
|
|
133
|
+
target: Module | None
|
|
134
|
+
if path.suffix != SUFFIX or (target := catalog.modules.get(module_name(path))) is None:
|
|
135
|
+
return {}
|
|
136
|
+
found: dict[str, Mapping[str, str]] = {}
|
|
137
|
+
name: str
|
|
138
|
+
bases: tuple[str, ...]
|
|
139
|
+
for name, bases in target.bases.items():
|
|
140
|
+
kinds: dict[str, str] = {}
|
|
141
|
+
base: str
|
|
142
|
+
for base in bases:
|
|
143
|
+
defined: str = "" if base in target.bases else plain.resolved(catalog.modules, target, base)
|
|
144
|
+
module: Module | None = catalog.modules.get(defined.rpartition(_DOT)[0])
|
|
145
|
+
if module is not None and not module.installed:
|
|
146
|
+
kinds.update(module.emptied.get(defined.rpartition(_DOT)[2], {}))
|
|
147
|
+
if kinds:
|
|
148
|
+
found[name] = kinds
|
|
149
|
+
return found
|
|
Binary file
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Unannotated parameters typed by their callers: what every call in the checked files passes each one.
|
|
3
|
+
|
|
4
|
+
Once every file is checked, each module's `Observed` calls to the checked files' open functions (see
|
|
5
|
+
`params.open_functions`) are matched to their parameters, as Python binds them. A parameter left
|
|
6
|
+
unannotated is typed when every call passes it, each an argument of the same known type
|
|
7
|
+
(`rules.calls` says which count); not if a call leaves it to its default, can't be matched, or
|
|
8
|
+
unpacks its arguments, nor if the function escapes (a callback), and never for a function no file
|
|
9
|
+
calls. The files defining them are then checked again knowing those types, as guesses (`callers`):
|
|
10
|
+
a caller outside the checked files may pass anything.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
14
|
+
from dataclasses import replace
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Final, TypeAlias
|
|
17
|
+
|
|
18
|
+
from constricter.fix.core.known import Call, Callee, Observed, Origin, Passed, Seeds
|
|
19
|
+
from constricter.fix.index import project
|
|
20
|
+
from constricter.fix.index.modules import SUFFIX, Index
|
|
21
|
+
from constricter.fix.index.params import Param
|
|
22
|
+
|
|
23
|
+
KIND: Final = "callers" # the guessing mechanism (`FIX_KINDS`)
|
|
24
|
+
# A module's functions' typed parameters: each one's type and what it rests on, by name, by function.
|
|
25
|
+
Parameters: TypeAlias = dict[str, dict[str, Passed]]
|
|
26
|
+
_POSITIONAL: Final = frozenset({"p", "e"})
|
|
27
|
+
_KEYWORD: Final = frozenset({"e", "k"})
|
|
28
|
+
_UNPASSED: Final = "" # a parameter a call leaves to its default
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def parameters(observations: Iterable[Observed], catalog: Index) -> dict[str, Parameters]:
|
|
32
|
+
"""Type the checked files' functions' unannotated parameters by every call to them (see the docstring).
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Each typed parameter's type, by its function and module.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
calls: dict[Callee, list[Call]] = {}
|
|
39
|
+
escaped: set[Callee] = set()
|
|
40
|
+
observed: Observed
|
|
41
|
+
for observed in observations:
|
|
42
|
+
escaped.update(observed.escaped)
|
|
43
|
+
callee: Callee
|
|
44
|
+
found: tuple[Call, ...]
|
|
45
|
+
for callee, found in observed.calls.items():
|
|
46
|
+
calls.setdefault(callee, []).extend(found)
|
|
47
|
+
typed: dict[str, Parameters] = {}
|
|
48
|
+
each: list[Call]
|
|
49
|
+
for callee, each in calls.items():
|
|
50
|
+
module: str
|
|
51
|
+
function: str
|
|
52
|
+
module, function = callee
|
|
53
|
+
params: tuple[Param, ...] | None = (
|
|
54
|
+
None
|
|
55
|
+
if callee in escaped or module not in catalog.modules
|
|
56
|
+
else catalog.modules[module].open.get(function)
|
|
57
|
+
)
|
|
58
|
+
types: dict[str, Passed]
|
|
59
|
+
if types := {} if params is None else _typed(params, each):
|
|
60
|
+
typed.setdefault(module, {})[function] = types
|
|
61
|
+
return typed
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _typed(params: Sequence[Param], calls: Sequence[Call]) -> dict[str, Passed]:
|
|
65
|
+
"""Type a function's unannotated parameters by `calls`: each one's, if every call passes it the same.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Each typed parameter's type, and what its arguments' types rest on together.
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
bound: list[dict[str, Passed | None] | None] = [_bound(params, call) for call in calls]
|
|
72
|
+
found: dict[str, Passed] = {}
|
|
73
|
+
param: Param
|
|
74
|
+
for param in params:
|
|
75
|
+
name: str = param[0]
|
|
76
|
+
passed: list[Passed | None] = [
|
|
77
|
+
None if one is None else one.get(name, (_UNPASSED, frozenset())) for one in bound
|
|
78
|
+
]
|
|
79
|
+
types: set[str | None] = {None if one is None else one[0] for one in passed}
|
|
80
|
+
only: str | None = types.pop() if len(types) == 1 else None
|
|
81
|
+
if not param[3] and only:
|
|
82
|
+
found[name] = (only, frozenset().union(*(one[1] for one in passed if one is not None)))
|
|
83
|
+
return found
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _bound(params: Sequence[Param], call: Call) -> dict[str, Passed | None] | None:
|
|
87
|
+
"""Bind a call's arguments' types to a function's parameters, as Python binds the arguments.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Each parameter's argument's type (`None`: unknown), by name, a parameter it leaves to its
|
|
91
|
+
default missing; or `None` if they can't be bound (unpacked, too many, an unknown keyword, one
|
|
92
|
+
bound twice): a call that fails, or can't be read.
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
positional: list[str] = [param[0] for param in params if param[1] in _POSITIONAL]
|
|
96
|
+
if call.unpacked or len(call.positional) > len(positional):
|
|
97
|
+
return None
|
|
98
|
+
bound: dict[str, Passed | None] = dict(zip(positional, call.positional, strict=False))
|
|
99
|
+
keyword: str
|
|
100
|
+
typed: Passed | None
|
|
101
|
+
for keyword, typed in call.keywords:
|
|
102
|
+
if keyword in bound or not any(param[0] == keyword and param[1] in _KEYWORD for param in params):
|
|
103
|
+
return None
|
|
104
|
+
bound[keyword] = typed
|
|
105
|
+
return bound
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def callees(catalog: project.Index, path: Path) -> dict[str, Callee]:
|
|
109
|
+
"""Find the checked files' functions the file at `path` may call whose parameters aren't all annotated.
|
|
110
|
+
|
|
111
|
+
Its own (`f`), and those it imports (`f`, `u.f`, through re-exports); not an installed package's.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Each, as the file spells it, mapped to its module and name; nothing for a file `catalog` doesn't
|
|
115
|
+
have.
|
|
116
|
+
|
|
117
|
+
"""
|
|
118
|
+
target: project.Module | None
|
|
119
|
+
if path.suffix != SUFFIX or (target := catalog.modules.get(project.module_name(path))) is None:
|
|
120
|
+
return {}
|
|
121
|
+
found: dict[str, Callee] = {name: (target.name, name) for name in target.open}
|
|
122
|
+
key: str
|
|
123
|
+
origin: Origin
|
|
124
|
+
for key, origin in project.spellings(catalog, target, project.OPEN):
|
|
125
|
+
defined: tuple[project.Module, str] | None = project.definition(catalog.modules, origin, project.OPEN)
|
|
126
|
+
if defined is not None and not defined[0].installed:
|
|
127
|
+
found[key] = (defined[0].name, defined[1])
|
|
128
|
+
return found
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def own_parameters(catalog: project.Index, path: Path) -> Seeds:
|
|
132
|
+
"""Find what every call passes the unannotated parameters of the file at `path`'s functions.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Each function's parameters' types, by name (see `with_parameters`).
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
target: project.Module | None = (
|
|
139
|
+
catalog.modules.get(project.module_name(path)) if path.suffix == SUFFIX else None
|
|
140
|
+
)
|
|
141
|
+
return {} if target is None else target.parameters
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def with_parameters(catalog: project.Index, found: Mapping[str, Seeds]) -> project.Index:
|
|
145
|
+
"""Record what every call passes checked modules' functions' unannotated parameters (by module name).
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
The index, with them.
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
modules: dict[str, project.Module] = dict(catalog.modules)
|
|
152
|
+
name: str
|
|
153
|
+
for name in found.keys() & modules.keys():
|
|
154
|
+
modules[name] = replace(modules[name], parameters=found[name])
|
|
155
|
+
return catalog._replace(modules=modules)
|
|
Binary file
|