python-constricter 0.4.1__cp312-cp312-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- constricter/__init__.py +55 -0
- constricter/__main__.py +26 -0
- constricter/_version.py +4 -0
- constricter/cli/__init__.py +2 -0
- constricter/cli/baseline.py +107 -0
- constricter/cli/collecting.py +54 -0
- constricter/cli/command.py +547 -0
- constricter/cli/config.py +322 -0
- constricter/cli/explain.py +104 -0
- constricter/cli/lsp/__init__.py +2 -0
- constricter/cli/lsp/connection.py +475 -0
- constricter/cli/lsp/edits.py +188 -0
- constricter/cli/lsp/guard.py +133 -0
- constricter/cli/lsp/hints.py +391 -0
- constricter/cli/lsp/protocol.py +192 -0
- constricter/cli/lsp/remembered.py +81 -0
- constricter/cli/lsp/stored.py +200 -0
- constricter/cli/options.py +799 -0
- constricter/cli/paths.py +151 -0
- constricter/cli/reading.py +36 -0
- constricter/cli/report.py +397 -0
- constricter/cli/rounds.py +328 -0
- constricter/cli/runs.py +90 -0
- constricter/cli/schedule.py +463 -0
- constricter/cli/traced.py +401 -0
- constricter/cli/workers.py +286 -0
- constricter/empty.py +66 -0
- constricter/fix/__init__.cp312-win32.pyd +0 -0
- constricter/fix/__init__.py +2 -0
- constricter/fix/core/__init__.cp312-win32.pyd +0 -0
- constricter/fix/core/__init__.py +2 -0
- constricter/fix/core/asked.cp312-win32.pyd +0 -0
- constricter/fix/core/asked.py +56 -0
- constricter/fix/core/fixes.cp312-win32.pyd +0 -0
- constricter/fix/core/fixes.py +317 -0
- constricter/fix/core/imports.cp312-win32.pyd +0 -0
- constricter/fix/core/imports.py +580 -0
- constricter/fix/core/inherited.cp312-win32.pyd +0 -0
- constricter/fix/core/inherited.py +311 -0
- constricter/fix/core/known.cp312-win32.pyd +0 -0
- constricter/fix/core/known.py +730 -0
- constricter/fix/core/signatures.cp312-win32.pyd +0 -0
- constricter/fix/core/signatures.py +132 -0
- constricter/fix/index/__init__.cp312-win32.pyd +0 -0
- constricter/fix/index/__init__.py +2 -0
- constricter/fix/index/awaits.cp312-win32.pyd +0 -0
- constricter/fix/index/awaits.py +90 -0
- constricter/fix/index/beyond.cp312-win32.pyd +0 -0
- constricter/fix/index/beyond.py +149 -0
- constricter/fix/index/callers.cp312-win32.pyd +0 -0
- constricter/fix/index/callers.py +155 -0
- constricter/fix/index/crossed.cp312-win32.pyd +0 -0
- constricter/fix/index/crossed.py +327 -0
- constricter/fix/index/decorated.cp312-win32.pyd +0 -0
- constricter/fix/index/decorated.py +140 -0
- constricter/fix/index/fixtures.cp312-win32.pyd +0 -0
- constricter/fix/index/fixtures.py +176 -0
- constricter/fix/index/linked.cp312-win32.pyd +0 -0
- constricter/fix/index/linked.py +142 -0
- constricter/fix/index/loose.cp312-win32.pyd +0 -0
- constricter/fix/index/loose.py +103 -0
- constricter/fix/index/managed.cp312-win32.pyd +0 -0
- constricter/fix/index/managed.py +63 -0
- constricter/fix/index/modules.cp312-win32.pyd +0 -0
- constricter/fix/index/modules.py +741 -0
- constricter/fix/index/offers.cp312-win32.pyd +0 -0
- constricter/fix/index/offers.py +252 -0
- constricter/fix/index/order.cp312-win32.pyd +0 -0
- constricter/fix/index/order.py +155 -0
- constricter/fix/index/origins.cp312-win32.pyd +0 -0
- constricter/fix/index/origins.py +238 -0
- constricter/fix/index/own_methods.cp312-win32.pyd +0 -0
- constricter/fix/index/own_methods.py +91 -0
- constricter/fix/index/own_overloads.cp312-win32.pyd +0 -0
- constricter/fix/index/own_overloads.py +70 -0
- constricter/fix/index/own_types.cp312-win32.pyd +0 -0
- constricter/fix/index/own_types.py +330 -0
- constricter/fix/index/params.cp312-win32.pyd +0 -0
- constricter/fix/index/params.py +81 -0
- constricter/fix/index/plain.cp312-win32.pyd +0 -0
- constricter/fix/index/plain.py +221 -0
- constricter/fix/index/project.cp312-win32.pyd +0 -0
- constricter/fix/index/project.py +797 -0
- constricter/fix/index/sides.cp312-win32.pyd +0 -0
- constricter/fix/index/sides.py +160 -0
- constricter/fix/index/strings.cp312-win32.pyd +0 -0
- constricter/fix/index/strings.py +46 -0
- constricter/fix/index/stubs/__init__.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/__init__.py +2 -0
- constricter/fix/index/stubs/atoms.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/atoms.py +425 -0
- constricter/fix/index/stubs/chained.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/chained.py +69 -0
- constricter/fix/index/stubs/classnames.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/classnames.py +124 -0
- constricter/fix/index/stubs/declared.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/declared.py +508 -0
- constricter/fix/index/stubs/installed.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/installed.py +425 -0
- constricter/fix/index/stubs/names.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/names.py +200 -0
- constricter/fix/index/stubs/read.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/read.py +70 -0
- constricter/fix/index/stubs/signatures.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/signatures.py +656 -0
- constricter/fix/index/stubs/stubbed.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/stubbed.py +208 -0
- constricter/fix/index/taken.cp312-win32.pyd +0 -0
- constricter/fix/index/taken.py +45 -0
- constricter/fix/index/tuples.cp312-win32.pyd +0 -0
- constricter/fix/index/tuples.py +112 -0
- constricter/fix/libraries/__init__.cp312-win32.pyd +0 -0
- constricter/fix/libraries/__init__.py +2 -0
- constricter/fix/libraries/library.cp312-win32.pyd +0 -0
- constricter/fix/libraries/library.py +420 -0
- constricter/fix/libraries/opened.cp312-win32.pyd +0 -0
- constricter/fix/libraries/opened.py +147 -0
- constricter/fix/libraries/overloads.cp312-win32.pyd +0 -0
- constricter/fix/libraries/overloads.py +960 -0
- constricter/fix/libraries/receivers.cp312-win32.pyd +0 -0
- constricter/fix/libraries/receivers.py +274 -0
- constricter/fix/libraries/stdlib.cp312-win32.pyd +0 -0
- constricter/fix/libraries/stdlib.py +752 -0
- constricter/fix/libraries/walks.cp312-win32.pyd +0 -0
- constricter/fix/libraries/walks.py +49 -0
- constricter/fix/tables/aliases.json +147 -0
- constricter/fix/tables/attributes.json +700 -0
- constricter/fix/tables/awaited.json +69 -0
- constricter/fix/tables/awaited_overloads.json +14 -0
- constricter/fix/tables/bases.json +888 -0
- constricter/fix/tables/classes.json +1941 -0
- constricter/fix/tables/elements.json +156 -0
- constricter/fix/tables/functions.json +58 -0
- constricter/fix/tables/generic_attributes.json +254 -0
- constricter/fix/tables/method_overloads.json +461 -0
- constricter/fix/tables/method_signatures.json +954 -0
- constricter/fix/tables/methods.json +954 -0
- constricter/fix/tables/overloads.json +540 -0
- constricter/fix/tables/platform.json +37 -0
- constricter/fix/tables/returns.json +1362 -0
- constricter/fix/tables/scalar_members.json +16 -0
- constricter/fix/tables/scalars.json +2707 -0
- constricter/fix/tables/source.json +28 -0
- constricter/fix/tables/subscriptable.json +77 -0
- constricter/fix/tables/type_parameters.json +178 -0
- constricter/fix/tables/variables.json +2962 -0
- constricter/fix/values/__init__.cp312-win32.pyd +0 -0
- constricter/fix/values/__init__.py +2 -0
- constricter/fix/values/aliased.cp312-win32.pyd +0 -0
- constricter/fix/values/aliased.py +195 -0
- constricter/fix/values/bodies.cp312-win32.pyd +0 -0
- constricter/fix/values/bodies.py +76 -0
- constricter/fix/values/callables.cp312-win32.pyd +0 -0
- constricter/fix/values/callables.py +323 -0
- constricter/fix/values/called.cp312-win32.pyd +0 -0
- constricter/fix/values/called.py +194 -0
- constricter/fix/values/classvars.cp312-win32.pyd +0 -0
- constricter/fix/values/classvars.py +629 -0
- constricter/fix/values/decided.cp312-win32.pyd +0 -0
- constricter/fix/values/decided.py +429 -0
- constricter/fix/values/displays.cp312-win32.pyd +0 -0
- constricter/fix/values/displays.py +101 -0
- constricter/fix/values/doubts.cp312-win32.pyd +0 -0
- constricter/fix/values/doubts.py +677 -0
- constricter/fix/values/entered.cp312-win32.pyd +0 -0
- constricter/fix/values/entered.py +332 -0
- constricter/fix/values/fills.cp312-win32.pyd +0 -0
- constricter/fix/values/fills.py +446 -0
- constricter/fix/values/guesses.cp312-win32.pyd +0 -0
- constricter/fix/values/guesses.py +376 -0
- constricter/fix/values/hinted.cp312-win32.pyd +0 -0
- constricter/fix/values/hinted.py +811 -0
- constricter/fix/values/inference.cp312-win32.pyd +0 -0
- constricter/fix/values/inference.py +698 -0
- constricter/fix/values/loops.cp312-win32.pyd +0 -0
- constricter/fix/values/loops.py +335 -0
- constricter/fix/values/members.cp312-win32.pyd +0 -0
- constricter/fix/values/members.py +578 -0
- constricter/fix/values/narrowed.cp312-win32.pyd +0 -0
- constricter/fix/values/narrowed.py +220 -0
- constricter/fix/values/operated.cp312-win32.pyd +0 -0
- constricter/fix/values/operated.py +202 -0
- constricter/fix/values/ordered.cp312-win32.pyd +0 -0
- constricter/fix/values/ordered.py +74 -0
- constricter/fix/values/returned.cp312-win32.pyd +0 -0
- constricter/fix/values/returned.py +479 -0
- constricter/fix/values/returns.cp312-win32.pyd +0 -0
- constricter/fix/values/returns.py +237 -0
- constricter/fix/values/shapes.cp312-win32.pyd +0 -0
- constricter/fix/values/shapes.py +654 -0
- constricter/fix/values/stored.cp312-win32.pyd +0 -0
- constricter/fix/values/stored.py +564 -0
- constricter/fix/values/targets.cp312-win32.pyd +0 -0
- constricter/fix/values/targets.py +419 -0
- constricter/fix/values/unchecked.cp312-win32.pyd +0 -0
- constricter/fix/values/unchecked.py +278 -0
- constricter/jsonc.py +50 -0
- constricter/noqa.py +47 -0
- constricter/notebook.py +115 -0
- constricter/offences.py +436 -0
- constricter/overrides.py +24 -0
- constricter/plugins/__init__.py +2 -0
- constricter/plugins/flake8.py +133 -0
- constricter/plugins/pylint.py +186 -0
- constricter/py.typed +0 -0
- constricter/recording.py +660 -0
- constricter/rules/__init__.cp312-win32.pyd +0 -0
- constricter/rules/__init__.py +2 -0
- constricter/rules/annotations.cp312-win32.pyd +0 -0
- constricter/rules/annotations.py +296 -0
- constricter/rules/binding.cp312-win32.pyd +0 -0
- constricter/rules/binding.py +652 -0
- constricter/rules/calls.cp312-win32.pyd +0 -0
- constricter/rules/calls.py +460 -0
- constricter/rules/checker.cp312-win32.pyd +0 -0
- constricter/rules/checker.py +536 -0
- constricter/rules/declarations.cp312-win32.pyd +0 -0
- constricter/rules/declarations.py +730 -0
- constricter/rules/decorators.cp312-win32.pyd +0 -0
- constricter/rules/decorators.py +351 -0
- constricter/rules/flow.cp312-win32.pyd +0 -0
- constricter/rules/flow.py +502 -0
- constricter/rules/keys.cp312-win32.pyd +0 -0
- constricter/rules/keys.py +79 -0
- constricter/rules/late.cp312-win32.pyd +0 -0
- constricter/rules/late.py +969 -0
- constricter/rules/narrowing.cp312-win32.pyd +0 -0
- constricter/rules/narrowing.py +98 -0
- constricter/rules/parsed.cp312-win32.pyd +0 -0
- constricter/rules/parsed.py +136 -0
- constricter/rules/quoted.cp312-win32.pyd +0 -0
- constricter/rules/quoted.py +141 -0
- constricter/rules/rebinding.cp312-win32.pyd +0 -0
- constricter/rules/rebinding.py +169 -0
- constricter/rules/recorded.cp312-win32.pyd +0 -0
- constricter/rules/recorded.py +168 -0
- constricter/rules/redundant.cp312-win32.pyd +0 -0
- constricter/rules/redundant.py +160 -0
- constricter/rules/scope.cp312-win32.pyd +0 -0
- constricter/rules/scope.py +802 -0
- constricter/rules/scoped.cp312-win32.pyd +0 -0
- constricter/rules/scoped.py +232 -0
- constricter/rules/scoping.cp312-win32.pyd +0 -0
- constricter/rules/scoping.py +346 -0
- constricter/rules/settings.cp312-win32.pyd +0 -0
- constricter/rules/settings.py +189 -0
- constricter/rules/syntax.cp312-win32.pyd +0 -0
- constricter/rules/syntax.py +472 -0
- constricter/rules/tables.cp312-win32.pyd +0 -0
- constricter/rules/tables.py +169 -0
- constricter/rules/walked.cp312-win32.pyd +0 -0
- constricter/rules/walked.py +127 -0
- constricter/rules/widened.cp312-win32.pyd +0 -0
- constricter/rules/widened.py +322 -0
- constricter/trace.py +374 -0
- e45ce06b2fdd5c43f782__mypyc.cp312-win32.pyd +0 -0
- python_constricter-0.4.1.dist-info/METADATA +400 -0
- python_constricter-0.4.1.dist-info/RECORD +261 -0
- python_constricter-0.4.1.dist-info/WHEEL +4 -0
- python_constricter-0.4.1.dist-info/entry_points.txt +5 -0
- python_constricter-0.4.1.dist-info/licenses/LICENSE.md +21 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""A check's rounds: every file, then again each one `--fix` changed in a way a second check sees.
|
|
3
|
+
|
|
4
|
+
Each round knows the index of what the files offer each other, the type checker's hints with
|
|
5
|
+
`--infer-with`, and the runs kept from the last.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import contextlib
|
|
9
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import TYPE_CHECKING, Final, NamedTuple, TypeAlias, cast
|
|
12
|
+
|
|
13
|
+
from constricter import notebook
|
|
14
|
+
from constricter.cli import collecting, schedule, traced
|
|
15
|
+
from constricter.cli.options import Guesses, Mode, Options
|
|
16
|
+
from constricter.cli.paths import STDIN
|
|
17
|
+
from constricter.cli.reading import read_text
|
|
18
|
+
from constricter.cli.runs import CheckRun, FileRun, merged
|
|
19
|
+
from constricter.cli.workers import Workers, running, started
|
|
20
|
+
from constricter.empty import Empty
|
|
21
|
+
from constricter.fix.core.known import Callee, Hints, Outside
|
|
22
|
+
from constricter.fix.index import callers, decorated, loose, plain, project
|
|
23
|
+
from constricter.fix.index.stubs import installed
|
|
24
|
+
from constricter.offences import Fix
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING: # slow to import, and only needed for `--infer-with`
|
|
27
|
+
from constricter.cli.lsp import hints
|
|
28
|
+
|
|
29
|
+
_PY: Final = ".py" # a module the index names (not a notebook)
|
|
30
|
+
_Hinted: TypeAlias = dict[Path, tuple[Hints, ...]] # each file's hints, a checker's each
|
|
31
|
+
HINT_ROUNDS: Final = 4 # with `--fix`: how many times a file is fixed, at most (see `settled`)
|
|
32
|
+
# The fix kinds a scope's check comes to late, once the scope is read: what was bound to such a
|
|
33
|
+
# name's value before had no type yet, and has one once the fix is written.
|
|
34
|
+
_LATE_KINDS: Final = frozenset({"optional", "filled"})
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def settled(
|
|
38
|
+
paths: Sequence[Path],
|
|
39
|
+
check: Callable[[Path, Outside], FileRun],
|
|
40
|
+
options: Options,
|
|
41
|
+
session: "hints.Session | None",
|
|
42
|
+
) -> list[FileRun]:
|
|
43
|
+
"""Check every file, then again each one `--fix` changed in a way a second check would see.
|
|
44
|
+
|
|
45
|
+
Until a round changes nothing, `HINT_ROUNDS` at most: a second pass is then left nothing. With
|
|
46
|
+
`--likely` alone, the files a round changed are indexed again, and the others checked again too
|
|
47
|
+
where that changes what they know from outside: what reads a guess another file declares now is
|
|
48
|
+
certain.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
What each file found, a file's rounds joined.
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
runs: list[FileRun]
|
|
55
|
+
modules: project.Index
|
|
56
|
+
runs, modules = _called_again(
|
|
57
|
+
paths,
|
|
58
|
+
checked_all(paths, check, options, session),
|
|
59
|
+
check,
|
|
60
|
+
options,
|
|
61
|
+
session,
|
|
62
|
+
)
|
|
63
|
+
fixing: bool = options.mode is Mode.FIX
|
|
64
|
+
onward: bool = fixing and session is None and options.guesses is Guesses.LIKELY
|
|
65
|
+
again: list[int] = [
|
|
66
|
+
index
|
|
67
|
+
for index, run in enumerate(runs)
|
|
68
|
+
if fixing and paths[index] != STDIN and _unsettled(run, session, options.guesses)
|
|
69
|
+
]
|
|
70
|
+
changed: list[int] = [at for at in range(len(runs)) if onward and _changed(paths[at], runs[at])]
|
|
71
|
+
for _ in range(HINT_ROUNDS - 1):
|
|
72
|
+
held: dict[int, FileRun] = {}
|
|
73
|
+
if changed:
|
|
74
|
+
modules = _reindexed(modules, [paths[i] for i in changed], options)
|
|
75
|
+
held = {i: run for i, run in enumerate(runs) if i not in again and paths[i] != STDIN}
|
|
76
|
+
if not again and not held:
|
|
77
|
+
break
|
|
78
|
+
fresh: list[tuple[int, FileRun]]
|
|
79
|
+
latest: project.Index
|
|
80
|
+
fresh, latest = _round(paths, runs, (again, held), (check, options, session), modules)
|
|
81
|
+
modules = latest if onward else modules
|
|
82
|
+
again = [index for index, run in fresh if _unsettled(run, session, options.guesses)]
|
|
83
|
+
changed = [index for index, run in fresh if _changed(paths[index], run)]
|
|
84
|
+
return runs
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _round(
|
|
88
|
+
paths: Sequence[Path],
|
|
89
|
+
runs: list[FileRun],
|
|
90
|
+
todo: tuple[Sequence[int], Mapping[int, FileRun]],
|
|
91
|
+
how: "tuple[Callable[[Path, Outside], FileRun], Options, hints.Session | None]",
|
|
92
|
+
modules: project.Index,
|
|
93
|
+
) -> tuple[list[tuple[int, FileRun]], project.Index]:
|
|
94
|
+
"""Check files of `paths` again, knowing `modules`: each one's new run is joined to its `runs`.
|
|
95
|
+
|
|
96
|
+
`todo`: the files to check, by index, and those held, each with its last run: checked only where
|
|
97
|
+
they know anything else from outside now (see `schedule.Rounds`). `how`: the check, the options
|
|
98
|
+
and the type checkers' session.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Each file the round checked, with this round's run alone; and the index, with what their
|
|
102
|
+
functions return.
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
again: Sequence[int]
|
|
106
|
+
held: Mapping[int, FileRun]
|
|
107
|
+
again, held = todo
|
|
108
|
+
which: list[int] = sorted({*again, *held})
|
|
109
|
+
redone: list[FileRun]
|
|
110
|
+
latest: project.Index
|
|
111
|
+
redone, latest = checked_all(
|
|
112
|
+
[paths[i] for i in which],
|
|
113
|
+
how[0],
|
|
114
|
+
how[1],
|
|
115
|
+
how[2],
|
|
116
|
+
_Known(modules, {at: held[i] for at, i in enumerate(which) if i in held}),
|
|
117
|
+
)
|
|
118
|
+
fresh: list[tuple[int, FileRun]] = [
|
|
119
|
+
(index, run) for index, run in zip(which, redone, strict=True) if run is not held.get(index)
|
|
120
|
+
]
|
|
121
|
+
index: int
|
|
122
|
+
run: FileRun
|
|
123
|
+
for index, run in fresh:
|
|
124
|
+
runs[index] = merged(runs[index], run)
|
|
125
|
+
return fresh, latest
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _changed(path: Path, run: FileRun) -> bool:
|
|
129
|
+
"""Check whether a round of `--fix` changed a file the index has (not standard input or a notebook).
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Whether it fixed anything there.
|
|
133
|
+
|
|
134
|
+
"""
|
|
135
|
+
return path.suffix == _PY and bool(cast("CheckRun", run).fixed)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _reindexed(modules: project.Index, paths: Sequence[Path], options: Options) -> project.Index:
|
|
139
|
+
"""Index again the files at `paths`, which `--fix` changed: in the run's workers, if it has them.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
The index, with them as they are now (see `project.spliced`).
|
|
143
|
+
|
|
144
|
+
"""
|
|
145
|
+
found: list[project.Module | None] = (
|
|
146
|
+
started(paths, options.jobs).read() if running() else [project.read(path) for path in paths]
|
|
147
|
+
)
|
|
148
|
+
return project.spliced(modules, found)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _unsettled(run: FileRun, session: "hints.Session | None", guesses: Guesses) -> bool:
|
|
152
|
+
"""Check whether a round of `--fix` changed a file in a way that changes what's inferred there.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Whether it fixed anything, with a type checker's hints: its new annotations change them.
|
|
156
|
+
Without, whether it made a fix the scope's check comes to late (`_LATE_KINDS`): what's bound
|
|
157
|
+
to such a name's value was read before its type was known. Or, with `--likely` alone, wrote a
|
|
158
|
+
guess: what rests on it and on no other guess is certain once it's declared.
|
|
159
|
+
|
|
160
|
+
"""
|
|
161
|
+
checked: CheckRun = cast("CheckRun", run)
|
|
162
|
+
return bool(checked.fixed) and (
|
|
163
|
+
session is not None
|
|
164
|
+
or any(
|
|
165
|
+
isinstance(made.offence.edit, Fix)
|
|
166
|
+
and (made.offence.edit.kinds & _LATE_KINDS or (guesses is Guesses.LIKELY and made.offence.unsafe))
|
|
167
|
+
for made in checked.made
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _called_again(
|
|
173
|
+
paths: Sequence[Path],
|
|
174
|
+
first: tuple[list[FileRun], project.Index],
|
|
175
|
+
check: Callable[[Path, Outside], FileRun],
|
|
176
|
+
options: Options,
|
|
177
|
+
session: "hints.Session | None" = None,
|
|
178
|
+
) -> tuple[list[FileRun], project.Index]:
|
|
179
|
+
"""Check again the files whose functions' unannotated parameters every call passes one type.
|
|
180
|
+
|
|
181
|
+
Knowing those types (see `callers`), as guesses; a file among several of one module name isn't
|
|
182
|
+
(which its callers call can't be told). Then the other files calling those functions, knowing
|
|
183
|
+
what they now return. With `--fix` a file's rounds are joined; otherwise the last, knowing more,
|
|
184
|
+
stands.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
What each file found, and the index, with the parameters' types.
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
runs: list[FileRun]
|
|
191
|
+
modules: project.Index
|
|
192
|
+
runs, modules = first
|
|
193
|
+
found: dict[str, callers.Parameters] = callers.parameters(
|
|
194
|
+
(run.calls for run in runs if isinstance(run, CheckRun)),
|
|
195
|
+
modules,
|
|
196
|
+
)
|
|
197
|
+
names: list[str | None] = [project.module_name(path) if path.suffix == _PY else None for path in paths]
|
|
198
|
+
again: list[int]
|
|
199
|
+
if not (again := [at for at, name in enumerate(names) if name in found and names.count(name) == 1]):
|
|
200
|
+
return runs, modules
|
|
201
|
+
calling: list[int] = _calling(runs, found, again)
|
|
202
|
+
before: project.Index = callers.with_parameters(modules, found)
|
|
203
|
+
modules = _checked_more(paths, runs, again, (check, options, session), before)
|
|
204
|
+
# A caller is checked again only if a function it imports now returns something else.
|
|
205
|
+
calling = [
|
|
206
|
+
at
|
|
207
|
+
for at in calling
|
|
208
|
+
if loose.returned(modules, paths[at], {}) != loose.returned(before, paths[at], {})
|
|
209
|
+
]
|
|
210
|
+
return runs, _checked_more(paths, runs, calling, (check, options, session), modules)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _checked_more(
|
|
214
|
+
paths: Sequence[Path],
|
|
215
|
+
runs: list[FileRun],
|
|
216
|
+
which: Sequence[int],
|
|
217
|
+
how: "tuple[Callable[[Path, Outside], FileRun], Options, hints.Session | None]",
|
|
218
|
+
modules: project.Index,
|
|
219
|
+
) -> project.Index:
|
|
220
|
+
"""Check the files of `paths` at `which` again, knowing `modules`, into `runs`.
|
|
221
|
+
|
|
222
|
+
`how`: the check, the options and the type checkers' session. With `--fix` a file's rounds are
|
|
223
|
+
joined; otherwise the later one stands.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
The index, with what their functions now return.
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
redone: list[FileRun]
|
|
230
|
+
redone, modules = checked_all([paths[at] for at in which], how[0], how[1], how[2], _Known(modules))
|
|
231
|
+
at: int
|
|
232
|
+
run: FileRun
|
|
233
|
+
for at, run in zip(which, redone, strict=True):
|
|
234
|
+
runs[at] = merged(runs[at], run) if how[1].mode is Mode.FIX else run
|
|
235
|
+
return modules
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _calling(
|
|
239
|
+
runs: Sequence[FileRun],
|
|
240
|
+
found: Mapping[str, callers.Parameters],
|
|
241
|
+
again: Sequence[int],
|
|
242
|
+
) -> list[int]:
|
|
243
|
+
"""Find the files (not among `again`) calling a function whose parameters `found` types.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Their indexes.
|
|
247
|
+
|
|
248
|
+
"""
|
|
249
|
+
typed: set[Callee] = {(module, function) for module, functions in found.items() for function in functions}
|
|
250
|
+
return [
|
|
251
|
+
at
|
|
252
|
+
for at, run in enumerate(runs)
|
|
253
|
+
if at not in again and isinstance(run, CheckRun) and typed & run.calls.calls.keys()
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class _Known(NamedTuple):
|
|
258
|
+
"""What a round of checks knows of the files: the index (`None`: index them), and their kept runs.
|
|
259
|
+
|
|
260
|
+
`kept`: see `schedule.Rounds`, by the index of each file in the round's.
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
modules: project.Index | None = None
|
|
264
|
+
kept: Mapping[int, FileRun] = Empty()
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
_FIRST: Final = _Known() # a run's first round: the files indexed, none kept
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def checked_all(
|
|
271
|
+
paths: Sequence[Path],
|
|
272
|
+
check: Callable[[Path, Outside], FileRun],
|
|
273
|
+
options: Options,
|
|
274
|
+
session: "hints.Session | None" = None,
|
|
275
|
+
known: _Known = _FIRST,
|
|
276
|
+
) -> tuple[list[FileRun], project.Index]:
|
|
277
|
+
"""Check `paths` (`--jobs` at a time), with the `session`'s hints, knowing `known`.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
What each file found, and the index of every file's module.
|
|
281
|
+
|
|
282
|
+
"""
|
|
283
|
+
# Before a worker starts: one forked while the servers' threads run could wait on a lock one holds.
|
|
284
|
+
hinted: _Hinted = {} if session is None else session.hints(_texts(paths))
|
|
285
|
+
if options.input.trace is not None:
|
|
286
|
+
hinted = traced.merged(hinted, traced.load(options.input.trace), paths)
|
|
287
|
+
coverage: bool = options.mode is Mode.COVERAGE # needs nothing from the other files
|
|
288
|
+
# A run's later rounds go to its workers, whatever their size: each knows what its files last knew.
|
|
289
|
+
pool: Workers | None = (
|
|
290
|
+
started(paths, options.jobs) if options.jobs != 1 and (len(paths) > 1 or running()) else None
|
|
291
|
+
)
|
|
292
|
+
modules: project.Index | None = known.modules
|
|
293
|
+
if modules is None and pool:
|
|
294
|
+
modules = project.Index({}, []) if coverage else pool.index()
|
|
295
|
+
elif modules is None:
|
|
296
|
+
modules = project.Index({}, []) if coverage else project.index(paths)
|
|
297
|
+
collecting.indexed()
|
|
298
|
+
if not coverage:
|
|
299
|
+
modules = plain.settled(
|
|
300
|
+
decorated.passed(installed.with_installed(modules, installed.search_path())),
|
|
301
|
+
options.checks.plain_bases,
|
|
302
|
+
)
|
|
303
|
+
return schedule.checked(
|
|
304
|
+
paths,
|
|
305
|
+
check,
|
|
306
|
+
pool,
|
|
307
|
+
(modules, hinted),
|
|
308
|
+
schedule.Rounds(merged if options.mode is Mode.FIX else None, known.kept),
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _texts(paths: Sequence[Path]) -> dict[Path, str]:
|
|
313
|
+
"""Read each file to ask the type checker about (not a notebook, or standard input).
|
|
314
|
+
|
|
315
|
+
A file that can't be read is left out: checking it reports that.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Each file's text.
|
|
319
|
+
|
|
320
|
+
"""
|
|
321
|
+
texts: dict[Path, str] = {}
|
|
322
|
+
path: Path
|
|
323
|
+
for path in paths:
|
|
324
|
+
if path != STDIN and path.suffix != notebook.SUFFIX:
|
|
325
|
+
# UnicodeDecodeError is a ValueError; a bad encoding declaration, a SyntaxError.
|
|
326
|
+
with contextlib.suppress(OSError, ValueError, SyntaxError):
|
|
327
|
+
texts[path] = read_text(path)
|
|
328
|
+
return texts
|
constricter/cli/runs.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""What checking one file gives the CLI, in each mode."""
|
|
3
|
+
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from dataclasses import dataclass, field, replace
|
|
6
|
+
from typing import TypeAlias, cast
|
|
7
|
+
|
|
8
|
+
from constricter.cli.report import Result
|
|
9
|
+
from constricter.fix.core.known import Observed, Returns
|
|
10
|
+
from constricter.offences import Offence
|
|
11
|
+
from constricter.rules.checker import Coverage
|
|
12
|
+
|
|
13
|
+
# The lines one round of `--fix` added to a file: for each, its cell (`None` outside a notebook)
|
|
14
|
+
# and the line (from 1) it went before.
|
|
15
|
+
Added: TypeAlias = tuple[tuple[int | None, int], ...]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class CheckRun:
|
|
20
|
+
"""What checking one file found (and fixed, or would fix), in check/fix/diff mode."""
|
|
21
|
+
|
|
22
|
+
results: list[Result] = field(default_factory=list[Result])
|
|
23
|
+
baselined: int = 0
|
|
24
|
+
fixed: int = 0 # --fix: how many offences it fixed
|
|
25
|
+
text: str = "" # --diff: the diff; --fix on standard input: the fixed source
|
|
26
|
+
error: str = ""
|
|
27
|
+
returned: Returns = field(default_factory=Returns) # what its unannotated functions return
|
|
28
|
+
calls: Observed = field(default_factory=Observed) # what it passes checked files' functions
|
|
29
|
+
# --fix: the offences it fixed, each where it was before any fix, and each round's added lines.
|
|
30
|
+
made: list[Result] = field(default_factory=list[Result])
|
|
31
|
+
rounds: tuple[Added, ...] = ()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class BaselineRun:
|
|
36
|
+
"""One file's offences, unfiltered, for --write-baseline."""
|
|
37
|
+
|
|
38
|
+
found: list[Offence] = field(default_factory=list[Offence])
|
|
39
|
+
error: str = ""
|
|
40
|
+
returned: Returns = field(default_factory=Returns) # what its unannotated functions return
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class CoverageRun:
|
|
45
|
+
"""One file's annotation coverage, for --coverage."""
|
|
46
|
+
|
|
47
|
+
coverage: Coverage | None = None
|
|
48
|
+
error: str = ""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
FileRun: TypeAlias = CheckRun | BaselineRun | CoverageRun
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def merged(before: FileRun, after: FileRun) -> FileRun:
|
|
55
|
+
"""Join a file's two `--fix` rounds' `CheckRun`s: what's left is the later's, what's fixed is both's.
|
|
56
|
+
|
|
57
|
+
The later round's fixes are placed on the lines they had before the earlier ones' (see `_original`).
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The joined run.
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
first: CheckRun = cast("CheckRun", before)
|
|
64
|
+
later: CheckRun = cast("CheckRun", after)
|
|
65
|
+
made: list[Result] = [
|
|
66
|
+
result._replace(offence=replace(result.offence, line=_original(result.offence, first.rounds)))
|
|
67
|
+
for result in later.made
|
|
68
|
+
]
|
|
69
|
+
return replace(
|
|
70
|
+
later,
|
|
71
|
+
fixed=first.fixed + later.fixed,
|
|
72
|
+
made=[*first.made, *made],
|
|
73
|
+
rounds=(*first.rounds, *later.rounds),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _original(offence: Offence, rounds: Sequence[Added]) -> int:
|
|
78
|
+
"""Find the line an offence was on before `rounds` of fixes added lines to its file.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
It; for an offence on an added line (a declaration, fixed again), the line that one went before.
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
line: int = offence.line
|
|
85
|
+
added: Added
|
|
86
|
+
for added in reversed(rounds):
|
|
87
|
+
before: list[int] = [at for cell, at in added if cell == offence.cell]
|
|
88
|
+
# The added lines above it: the first went where it says, each next one a line further down.
|
|
89
|
+
line -= sum(at + index < line for index, at in enumerate(before))
|
|
90
|
+
return line
|