python-constricter 0.4.1__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- constricter/__init__.py +55 -0
- constricter/__main__.py +26 -0
- constricter/_version.py +4 -0
- constricter/cli/__init__.py +2 -0
- constricter/cli/baseline.py +107 -0
- constricter/cli/collecting.py +54 -0
- constricter/cli/command.py +547 -0
- constricter/cli/config.py +322 -0
- constricter/cli/explain.py +104 -0
- constricter/cli/lsp/__init__.py +2 -0
- constricter/cli/lsp/connection.py +475 -0
- constricter/cli/lsp/edits.py +188 -0
- constricter/cli/lsp/guard.py +133 -0
- constricter/cli/lsp/hints.py +391 -0
- constricter/cli/lsp/protocol.py +192 -0
- constricter/cli/lsp/remembered.py +81 -0
- constricter/cli/lsp/stored.py +200 -0
- constricter/cli/options.py +799 -0
- constricter/cli/paths.py +151 -0
- constricter/cli/reading.py +36 -0
- constricter/cli/report.py +397 -0
- constricter/cli/rounds.py +328 -0
- constricter/cli/runs.py +90 -0
- constricter/cli/schedule.py +463 -0
- constricter/cli/traced.py +401 -0
- constricter/cli/workers.py +286 -0
- constricter/empty.py +66 -0
- constricter/fix/__init__.cp312-win_amd64.pyd +0 -0
- constricter/fix/__init__.py +2 -0
- constricter/fix/core/__init__.cp312-win_amd64.pyd +0 -0
- constricter/fix/core/__init__.py +2 -0
- constricter/fix/core/asked.cp312-win_amd64.pyd +0 -0
- constricter/fix/core/asked.py +56 -0
- constricter/fix/core/fixes.cp312-win_amd64.pyd +0 -0
- constricter/fix/core/fixes.py +317 -0
- constricter/fix/core/imports.cp312-win_amd64.pyd +0 -0
- constricter/fix/core/imports.py +580 -0
- constricter/fix/core/inherited.cp312-win_amd64.pyd +0 -0
- constricter/fix/core/inherited.py +311 -0
- constricter/fix/core/known.cp312-win_amd64.pyd +0 -0
- constricter/fix/core/known.py +730 -0
- constricter/fix/core/signatures.cp312-win_amd64.pyd +0 -0
- constricter/fix/core/signatures.py +132 -0
- constricter/fix/index/__init__.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/__init__.py +2 -0
- constricter/fix/index/awaits.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/awaits.py +90 -0
- constricter/fix/index/beyond.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/beyond.py +149 -0
- constricter/fix/index/callers.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/callers.py +155 -0
- constricter/fix/index/crossed.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/crossed.py +327 -0
- constricter/fix/index/decorated.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/decorated.py +140 -0
- constricter/fix/index/fixtures.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/fixtures.py +176 -0
- constricter/fix/index/linked.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/linked.py +142 -0
- constricter/fix/index/loose.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/loose.py +103 -0
- constricter/fix/index/managed.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/managed.py +63 -0
- constricter/fix/index/modules.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/modules.py +741 -0
- constricter/fix/index/offers.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/offers.py +252 -0
- constricter/fix/index/order.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/order.py +155 -0
- constricter/fix/index/origins.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/origins.py +238 -0
- constricter/fix/index/own_methods.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/own_methods.py +91 -0
- constricter/fix/index/own_overloads.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/own_overloads.py +70 -0
- constricter/fix/index/own_types.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/own_types.py +330 -0
- constricter/fix/index/params.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/params.py +81 -0
- constricter/fix/index/plain.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/plain.py +221 -0
- constricter/fix/index/project.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/project.py +797 -0
- constricter/fix/index/sides.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/sides.py +160 -0
- constricter/fix/index/strings.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/strings.py +46 -0
- constricter/fix/index/stubs/__init__.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/__init__.py +2 -0
- constricter/fix/index/stubs/atoms.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/atoms.py +425 -0
- constricter/fix/index/stubs/chained.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/chained.py +69 -0
- constricter/fix/index/stubs/classnames.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/classnames.py +124 -0
- constricter/fix/index/stubs/declared.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/declared.py +508 -0
- constricter/fix/index/stubs/installed.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/installed.py +425 -0
- constricter/fix/index/stubs/names.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/names.py +200 -0
- constricter/fix/index/stubs/read.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/read.py +70 -0
- constricter/fix/index/stubs/signatures.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/signatures.py +656 -0
- constricter/fix/index/stubs/stubbed.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/stubbed.py +208 -0
- constricter/fix/index/taken.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/taken.py +45 -0
- constricter/fix/index/tuples.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/tuples.py +112 -0
- constricter/fix/libraries/__init__.cp312-win_amd64.pyd +0 -0
- constricter/fix/libraries/__init__.py +2 -0
- constricter/fix/libraries/library.cp312-win_amd64.pyd +0 -0
- constricter/fix/libraries/library.py +420 -0
- constricter/fix/libraries/opened.cp312-win_amd64.pyd +0 -0
- constricter/fix/libraries/opened.py +147 -0
- constricter/fix/libraries/overloads.cp312-win_amd64.pyd +0 -0
- constricter/fix/libraries/overloads.py +960 -0
- constricter/fix/libraries/receivers.cp312-win_amd64.pyd +0 -0
- constricter/fix/libraries/receivers.py +274 -0
- constricter/fix/libraries/stdlib.cp312-win_amd64.pyd +0 -0
- constricter/fix/libraries/stdlib.py +752 -0
- constricter/fix/libraries/walks.cp312-win_amd64.pyd +0 -0
- constricter/fix/libraries/walks.py +49 -0
- constricter/fix/tables/aliases.json +147 -0
- constricter/fix/tables/attributes.json +700 -0
- constricter/fix/tables/awaited.json +69 -0
- constricter/fix/tables/awaited_overloads.json +14 -0
- constricter/fix/tables/bases.json +888 -0
- constricter/fix/tables/classes.json +1941 -0
- constricter/fix/tables/elements.json +156 -0
- constricter/fix/tables/functions.json +58 -0
- constricter/fix/tables/generic_attributes.json +254 -0
- constricter/fix/tables/method_overloads.json +461 -0
- constricter/fix/tables/method_signatures.json +954 -0
- constricter/fix/tables/methods.json +954 -0
- constricter/fix/tables/overloads.json +540 -0
- constricter/fix/tables/platform.json +37 -0
- constricter/fix/tables/returns.json +1362 -0
- constricter/fix/tables/scalar_members.json +16 -0
- constricter/fix/tables/scalars.json +2707 -0
- constricter/fix/tables/source.json +28 -0
- constricter/fix/tables/subscriptable.json +77 -0
- constricter/fix/tables/type_parameters.json +178 -0
- constricter/fix/tables/variables.json +2962 -0
- constricter/fix/values/__init__.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/__init__.py +2 -0
- constricter/fix/values/aliased.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/aliased.py +195 -0
- constricter/fix/values/bodies.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/bodies.py +76 -0
- constricter/fix/values/callables.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/callables.py +323 -0
- constricter/fix/values/called.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/called.py +194 -0
- constricter/fix/values/classvars.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/classvars.py +629 -0
- constricter/fix/values/decided.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/decided.py +429 -0
- constricter/fix/values/displays.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/displays.py +101 -0
- constricter/fix/values/doubts.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/doubts.py +677 -0
- constricter/fix/values/entered.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/entered.py +332 -0
- constricter/fix/values/fills.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/fills.py +446 -0
- constricter/fix/values/guesses.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/guesses.py +376 -0
- constricter/fix/values/hinted.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/hinted.py +811 -0
- constricter/fix/values/inference.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/inference.py +698 -0
- constricter/fix/values/loops.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/loops.py +335 -0
- constricter/fix/values/members.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/members.py +578 -0
- constricter/fix/values/narrowed.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/narrowed.py +220 -0
- constricter/fix/values/operated.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/operated.py +202 -0
- constricter/fix/values/ordered.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/ordered.py +74 -0
- constricter/fix/values/returned.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/returned.py +479 -0
- constricter/fix/values/returns.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/returns.py +237 -0
- constricter/fix/values/shapes.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/shapes.py +654 -0
- constricter/fix/values/stored.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/stored.py +564 -0
- constricter/fix/values/targets.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/targets.py +419 -0
- constricter/fix/values/unchecked.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/unchecked.py +278 -0
- constricter/jsonc.py +50 -0
- constricter/noqa.py +47 -0
- constricter/notebook.py +115 -0
- constricter/offences.py +436 -0
- constricter/overrides.py +24 -0
- constricter/plugins/__init__.py +2 -0
- constricter/plugins/flake8.py +133 -0
- constricter/plugins/pylint.py +186 -0
- constricter/py.typed +0 -0
- constricter/recording.py +660 -0
- constricter/rules/__init__.cp312-win_amd64.pyd +0 -0
- constricter/rules/__init__.py +2 -0
- constricter/rules/annotations.cp312-win_amd64.pyd +0 -0
- constricter/rules/annotations.py +296 -0
- constricter/rules/binding.cp312-win_amd64.pyd +0 -0
- constricter/rules/binding.py +652 -0
- constricter/rules/calls.cp312-win_amd64.pyd +0 -0
- constricter/rules/calls.py +460 -0
- constricter/rules/checker.cp312-win_amd64.pyd +0 -0
- constricter/rules/checker.py +536 -0
- constricter/rules/declarations.cp312-win_amd64.pyd +0 -0
- constricter/rules/declarations.py +730 -0
- constricter/rules/decorators.cp312-win_amd64.pyd +0 -0
- constricter/rules/decorators.py +351 -0
- constricter/rules/flow.cp312-win_amd64.pyd +0 -0
- constricter/rules/flow.py +502 -0
- constricter/rules/keys.cp312-win_amd64.pyd +0 -0
- constricter/rules/keys.py +79 -0
- constricter/rules/late.cp312-win_amd64.pyd +0 -0
- constricter/rules/late.py +969 -0
- constricter/rules/narrowing.cp312-win_amd64.pyd +0 -0
- constricter/rules/narrowing.py +98 -0
- constricter/rules/parsed.cp312-win_amd64.pyd +0 -0
- constricter/rules/parsed.py +136 -0
- constricter/rules/quoted.cp312-win_amd64.pyd +0 -0
- constricter/rules/quoted.py +141 -0
- constricter/rules/rebinding.cp312-win_amd64.pyd +0 -0
- constricter/rules/rebinding.py +169 -0
- constricter/rules/recorded.cp312-win_amd64.pyd +0 -0
- constricter/rules/recorded.py +168 -0
- constricter/rules/redundant.cp312-win_amd64.pyd +0 -0
- constricter/rules/redundant.py +160 -0
- constricter/rules/scope.cp312-win_amd64.pyd +0 -0
- constricter/rules/scope.py +802 -0
- constricter/rules/scoped.cp312-win_amd64.pyd +0 -0
- constricter/rules/scoped.py +232 -0
- constricter/rules/scoping.cp312-win_amd64.pyd +0 -0
- constricter/rules/scoping.py +346 -0
- constricter/rules/settings.cp312-win_amd64.pyd +0 -0
- constricter/rules/settings.py +189 -0
- constricter/rules/syntax.cp312-win_amd64.pyd +0 -0
- constricter/rules/syntax.py +472 -0
- constricter/rules/tables.cp312-win_amd64.pyd +0 -0
- constricter/rules/tables.py +169 -0
- constricter/rules/walked.cp312-win_amd64.pyd +0 -0
- constricter/rules/walked.py +127 -0
- constricter/rules/widened.cp312-win_amd64.pyd +0 -0
- constricter/rules/widened.py +322 -0
- constricter/trace.py +374 -0
- e45ce06b2fdd5c43f782__mypyc.cp312-win_amd64.pyd +0 -0
- python_constricter-0.4.1.dist-info/METADATA +400 -0
- python_constricter-0.4.1.dist-info/RECORD +261 -0
- python_constricter-0.4.1.dist-info/WHEEL +4 -0
- python_constricter-0.4.1.dist-info/entry_points.txt +5 -0
- python_constricter-0.4.1.dist-info/licenses/LICENSE.md +21 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Which of `--fix`'s inferences are guesses (`--unsafe-fixes`), and what each rests on."""
|
|
3
|
+
|
|
4
|
+
import ast
|
|
5
|
+
from collections.abc import Iterator, Mapping
|
|
6
|
+
from typing import Final, cast
|
|
7
|
+
|
|
8
|
+
from constricter.fix.core.known import PARAM, Known, stood
|
|
9
|
+
from constricter.fix.libraries import stdlib, walks
|
|
10
|
+
from constricter.fix.libraries.library import installed_method, library_class
|
|
11
|
+
from constricter.fix.libraries.opened import opened, opened_path
|
|
12
|
+
from constricter.fix.values import called, decided, displays, shapes
|
|
13
|
+
from constricter.fix.values.inference import (
|
|
14
|
+
CONTAINER_BUILDERS,
|
|
15
|
+
RETURNED,
|
|
16
|
+
inference,
|
|
17
|
+
inferred,
|
|
18
|
+
joined,
|
|
19
|
+
scalar,
|
|
20
|
+
)
|
|
21
|
+
from constricter.fix.values.loops import COMPREHENSIONS, dict_view, targets_typed
|
|
22
|
+
from constricter.fix.values.members import (
|
|
23
|
+
assigned_owner,
|
|
24
|
+
awaited,
|
|
25
|
+
awaited_origins,
|
|
26
|
+
class_variable,
|
|
27
|
+
member,
|
|
28
|
+
partial_method,
|
|
29
|
+
present,
|
|
30
|
+
returned_method,
|
|
31
|
+
)
|
|
32
|
+
from constricter.fix.values.returns import BUILTIN_RETURNS
|
|
33
|
+
from constricter.fix.values.targets import DICT_VIEWS, ITERATORS
|
|
34
|
+
from constricter.offences import CONSTRUCTOR, MEMBER
|
|
35
|
+
from constricter.rules.annotations import dotted
|
|
36
|
+
from constricter.rules.walked import children
|
|
37
|
+
|
|
38
|
+
# Builtins whose call is certain (when the module doesn't rebind the name): see `_is_guess`.
|
|
39
|
+
_CERTAIN_BUILTINS: Final = frozenset(
|
|
40
|
+
BUILTIN_RETURNS.keys() | CONTAINER_BUILDERS.keys() | ITERATORS | {"type", "getattr"},
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def guessed(
|
|
45
|
+
value: ast.expr,
|
|
46
|
+
known: Known,
|
|
47
|
+
guesses: frozenset[str],
|
|
48
|
+
declared: Mapping[str, str],
|
|
49
|
+
) -> bool:
|
|
50
|
+
"""Whether `inferred`'s annotation for `value` is a guess (`--unsafe-fixes`): it calls a class.
|
|
51
|
+
|
|
52
|
+
A capitalised call may construct a generic class (`Box(1)` is really `Box[int]`) or be a factory
|
|
53
|
+
function; literals, calls to a module function, a fixed-return builtin (`len`, `isinstance`,
|
|
54
|
+
...) or a method `members.member` types on a value whose type is known, and another local this
|
|
55
|
+
scope already typed, are certain. Copying a local `inferred` itself only
|
|
56
|
+
guessed (`guesses`) is no more certain than the guess it copies, unless the value's type is the
|
|
57
|
+
same without it (see `_rests`).
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Whether any call in `value` is to something other than such a certain callee, or its type
|
|
61
|
+
rests on a name in it that copies such a guess.
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
return guessing(value, known, guesses, {}, declared)[0]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _deciding(value: ast.AST, known: Known, declared: Mapping[str, str]) -> Iterator[ast.AST]:
|
|
68
|
+
"""Walk what decides `value`'s type: all of it, but not the arguments of a call they can't change.
|
|
69
|
+
|
|
70
|
+
`open(path, "rb")` is a file object by its mode, `logging.getLogger(name)` a `Logger`, whatever
|
|
71
|
+
`path` or `name` are: a guess there doesn't make the call's type one; nor in `x.kind is None`, a
|
|
72
|
+
`bool` whatever `x.kind` is, or an f-string. Nor does it in a call to a
|
|
73
|
+
fixed-return builtin (`len(Box())`), a function declaring its return, or a method a certain
|
|
74
|
+
source types (`"{}".format(Box())`, `self.items.get(key())`), though its receiver still counts.
|
|
75
|
+
|
|
76
|
+
Yields:
|
|
77
|
+
Each node.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
# A stack, not a recursion: a nested generator passes each node up through every level above it.
|
|
81
|
+
waiting: list[ast.AST | None] = [None, value] # `None` at its bottom ends it
|
|
82
|
+
node: ast.AST
|
|
83
|
+
for node in iter(waiting.pop, None):
|
|
84
|
+
yield node
|
|
85
|
+
if isinstance(node, ast.expr) and scalar(node) is not None:
|
|
86
|
+
continue
|
|
87
|
+
if isinstance(node, ast.Call) and (opened(node, known) or library_class(node, known)):
|
|
88
|
+
continue
|
|
89
|
+
read: ast.Attribute | None = shapes.named_read(node, known) if isinstance(node, ast.Call) else None
|
|
90
|
+
if isinstance(node, ast.Call) and fixed_by_callee(node, known, declared):
|
|
91
|
+
waiting.append(node.func)
|
|
92
|
+
elif read is not None:
|
|
93
|
+
# Typed as the read it makes, `obj.name`: an attribute nothing declares is a guess there too.
|
|
94
|
+
waiting.extend(reversed([read, *cast("ast.Call", node).args[2:]]))
|
|
95
|
+
else:
|
|
96
|
+
waiting.extend(reversed(children(node)))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def fixed_by_callee(call: ast.Call, known: Known, declared: Mapping[str, str]) -> bool:
|
|
100
|
+
"""Check whether `call`'s type is its callee's alone, whatever its arguments are.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Whether it is: a fixed-return builtin the module doesn't rebind, a function declaring its
|
|
104
|
+
return (`Known.calls`), or a method a certain source types (see `certain_method`).
|
|
105
|
+
|
|
106
|
+
"""
|
|
107
|
+
name: str
|
|
108
|
+
func: ast.expr
|
|
109
|
+
match call:
|
|
110
|
+
case ast.Call(func=ast.Name(id=name)) if name in BUILTIN_RETURNS and known.is_builtin(name):
|
|
111
|
+
return True
|
|
112
|
+
case ast.Call(func=ast.Name() | ast.Attribute() as func) if (
|
|
113
|
+
dotted(func) in known.calls or dotted(func) in known.indirect.partial.calls
|
|
114
|
+
):
|
|
115
|
+
return True
|
|
116
|
+
case _:
|
|
117
|
+
return certain_method(call, known, declared)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def guessing(
|
|
121
|
+
value: ast.expr,
|
|
122
|
+
known: Known,
|
|
123
|
+
guesses: frozenset[str],
|
|
124
|
+
origins: Mapping[str, frozenset[str]],
|
|
125
|
+
declared: Mapping[str, str],
|
|
126
|
+
) -> tuple[bool, frozenset[str]]:
|
|
127
|
+
"""Work out, in one pass, whether `value`'s type is a guess (see `guessed`), and what it rests on.
|
|
128
|
+
|
|
129
|
+
`origins` holds what each guessed local (of `guesses`) rests on.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Whether it's a guess; and if it is, its guessing mechanisms (`FIX_KINDS`): `returned` for a
|
|
133
|
+
method typed only by its `return`s, `constructor` for any other guessed call, and each guessed
|
|
134
|
+
local's own.
|
|
135
|
+
|
|
136
|
+
"""
|
|
137
|
+
# One walk, for both: the comprehensions whose targets the rest may use, then each node.
|
|
138
|
+
walked: list[ast.AST] = list(_deciding(stood(value, declared), known, declared))
|
|
139
|
+
inside: Mapping[str, str] = targets_typed(
|
|
140
|
+
[node for node in walked if isinstance(node, COMPREHENSIONS)],
|
|
141
|
+
known,
|
|
142
|
+
declared,
|
|
143
|
+
inference,
|
|
144
|
+
)
|
|
145
|
+
unsafe: bool = False
|
|
146
|
+
named: bool = True # only names are guesses, so far
|
|
147
|
+
found: set[str] = set()
|
|
148
|
+
node: ast.AST
|
|
149
|
+
for node in walked:
|
|
150
|
+
if joined(node, known, declared):
|
|
151
|
+
unsafe, named = True, False
|
|
152
|
+
found.add(displays.JOINED)
|
|
153
|
+
if _is_guess(node, known, guesses, inside):
|
|
154
|
+
unsafe = True
|
|
155
|
+
named = named and isinstance(node, ast.Name)
|
|
156
|
+
if isinstance(node, ast.Call):
|
|
157
|
+
found.update(_guessed_by(node, known, inside))
|
|
158
|
+
elif isinstance(node, ast.Attribute):
|
|
159
|
+
found.update(_assigned_origins(node, known, inside) or ())
|
|
160
|
+
found.update(origins.get(_read(node), ()))
|
|
161
|
+
if not unsafe or (named and not _rests(value, known, guesses, declared)):
|
|
162
|
+
return False, frozenset()
|
|
163
|
+
return True, frozenset(found)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _rests(value: ast.expr, known: Known, guesses: frozenset[str], declared: Mapping[str, str]) -> bool:
|
|
167
|
+
"""Check whether `value`'s type rests on the guessed names in it.
|
|
168
|
+
|
|
169
|
+
`os.path.join(root, "x")` is a `str` by its literal, whatever `root` is: a guessed `root` doesn't
|
|
170
|
+
make it a guess.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Whether it has another type, or none, with those names' types unknown.
|
|
174
|
+
|
|
175
|
+
"""
|
|
176
|
+
sure: dict[str, str] = {name: text for name, text in declared.items() if name not in guesses}
|
|
177
|
+
return inferred(value, known, sure) != inferred(value, known, declared)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _receiving(receiver: ast.expr, method: str, known: Known, declared: Mapping[str, str]) -> str | None:
|
|
181
|
+
"""Type the receiver of a call of `method`, as its member is looked up on it (see `members.present`).
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Its type, or `None` if it isn't known.
|
|
185
|
+
|
|
186
|
+
"""
|
|
187
|
+
typed: str | None = inferred(receiver, known, declared)
|
|
188
|
+
return None if typed is None else present(typed, method)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def certain_method(call: ast.expr, known: Known, declared: Mapping[str, str]) -> bool:
|
|
192
|
+
"""Check whether `call` is a method call a certain source types on its receiver's type.
|
|
193
|
+
|
|
194
|
+
A member `members.member` knows (or one declared to return a vague part, see `Partial`), or a
|
|
195
|
+
`dict`'s `.keys()`, `.values()` or `.items()`; its arguments can't change it (the receiver
|
|
196
|
+
itself may still be a guess).
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Whether it is.
|
|
200
|
+
|
|
201
|
+
"""
|
|
202
|
+
receiver: ast.expr
|
|
203
|
+
method: str
|
|
204
|
+
match call:
|
|
205
|
+
case ast.Call(func=ast.Attribute(value=receiver, attr=method)):
|
|
206
|
+
typed: str | None = _receiving(receiver, method, known, declared)
|
|
207
|
+
return typed is not None and (
|
|
208
|
+
member(typed, method, call, known) is not None
|
|
209
|
+
or partial_method(typed, method, known) is not None
|
|
210
|
+
or (
|
|
211
|
+
method in DICT_VIEWS
|
|
212
|
+
and dict_view(receiver, method, known, declared, inference) is not None
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
case _:
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _overloaded_method(call: ast.Call, known: Known, declared: Mapping[str, str]) -> bool:
|
|
220
|
+
"""Check whether `call` calls a method its arguments type: the standard library's, or `dict.get`.
|
|
221
|
+
|
|
222
|
+
See `stdlib.overloaded_method` and `shapes.defaulted`. Its arguments are walked as its parts: a
|
|
223
|
+
guessed one makes it a guess.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Whether it does.
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
receiver: ast.expr
|
|
230
|
+
method: str
|
|
231
|
+
match call:
|
|
232
|
+
case ast.Call(func=ast.Attribute(value=receiver, attr=method)):
|
|
233
|
+
typed: str | None = _receiving(receiver, method, known, declared)
|
|
234
|
+
base: str = known.class_side.lineage.definer(typed or "", method) or typed or ""
|
|
235
|
+
return typed is not None and (
|
|
236
|
+
stdlib.overloaded_method(typed, method, known) is not None
|
|
237
|
+
or stdlib.overloaded_method(base, method, known) is not None # a library base's
|
|
238
|
+
or installed_method(typed, method, known) is not None
|
|
239
|
+
or installed_method(base, method, known) is not None
|
|
240
|
+
or shapes.defaulted(typed, call, lambda arg: inference(arg, known, declared), known)
|
|
241
|
+
is not None
|
|
242
|
+
or opened_path(typed, call, known) is not None
|
|
243
|
+
)
|
|
244
|
+
case _:
|
|
245
|
+
return False
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _guessed_by(call: ast.Call, known: Known, declared: Mapping[str, str]) -> frozenset[str]:
|
|
249
|
+
"""Name what makes one guessed call a guess.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
A method typed by its `return`s: `returned`, and what those rest on; a function whose
|
|
253
|
+
`return`s are guesses: what they rest on; anything else: `constructor`.
|
|
254
|
+
|
|
255
|
+
"""
|
|
256
|
+
func: ast.expr
|
|
257
|
+
callee: str | None
|
|
258
|
+
receiver: ast.expr
|
|
259
|
+
method: str
|
|
260
|
+
typed: str | None
|
|
261
|
+
defined: tuple[str, str] | None
|
|
262
|
+
rests: frozenset[str] | None
|
|
263
|
+
match call:
|
|
264
|
+
case _ if rests := awaited_origins(call, known, lambda arg: inference(arg, known, declared)):
|
|
265
|
+
return rests
|
|
266
|
+
case ast.Call(func=ast.Name() | ast.Attribute() as func) if (
|
|
267
|
+
callee := dotted(func)
|
|
268
|
+
) in known.returned.guesses:
|
|
269
|
+
return known.returned.guesses[callee or ""]
|
|
270
|
+
case ast.Call(func=ast.Attribute(value=receiver, attr=method)) if (
|
|
271
|
+
typed := _receiving(receiver, method, known, declared)
|
|
272
|
+
) is not None and (defined := returned_method(typed, method, known)) is not None:
|
|
273
|
+
return frozenset({RETURNED}) | known.returned.guesses.get(f"{defined[0]}.{method}", frozenset())
|
|
274
|
+
case _:
|
|
275
|
+
return frozenset({CONSTRUCTOR})
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _assigned_origins(
|
|
279
|
+
node: ast.Attribute,
|
|
280
|
+
known: Known,
|
|
281
|
+
declared: Mapping[str, str],
|
|
282
|
+
) -> frozenset[str] | None:
|
|
283
|
+
"""Name what an attribute nothing declares rests on: its assignments, or its class's body.
|
|
284
|
+
|
|
285
|
+
One typed only by its assignments (see `Returned.attributes`), or a plain class's variable typed
|
|
286
|
+
by its value (see `ClassSide.variables`).
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
`assigned`, and what its values' guesses rest on, or `member`; or `None` if it isn't one (a
|
|
290
|
+
certain source types it, or nothing does).
|
|
291
|
+
|
|
292
|
+
"""
|
|
293
|
+
if not any(
|
|
294
|
+
node.attr in attributes
|
|
295
|
+
for attributes in (*known.returned.attributes.values(), *known.class_side.variables.values())
|
|
296
|
+
):
|
|
297
|
+
return None # most attributes: no receiver to type
|
|
298
|
+
typed: str | None = inferred(node.value, known, declared)
|
|
299
|
+
if typed is None or member(typed, node.attr, None, known) is not None:
|
|
300
|
+
return None
|
|
301
|
+
owner: str | None
|
|
302
|
+
if (owner := assigned_owner(typed, node.attr, known)) is not None:
|
|
303
|
+
return known.returned.guesses[f"{owner}.{node.attr}"]
|
|
304
|
+
return None if class_variable(typed, node.attr, known) is None else frozenset({MEMBER})
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _is_guess(
|
|
308
|
+
node: ast.AST,
|
|
309
|
+
known: Known,
|
|
310
|
+
guesses: frozenset[str],
|
|
311
|
+
declared: Mapping[str, str],
|
|
312
|
+
) -> bool:
|
|
313
|
+
name: str
|
|
314
|
+
func: ast.expr
|
|
315
|
+
match node:
|
|
316
|
+
case ast.Call(func=ast.Name(id=name)) if (
|
|
317
|
+
name in _CERTAIN_BUILTINS and known.is_builtin(name)
|
|
318
|
+
) or name in known.indirect.awaits:
|
|
319
|
+
return False
|
|
320
|
+
# But an `async def`'s call typed by `return`s that are guesses, awaited: as they are.
|
|
321
|
+
case ast.Call(func=func) if not awaited_origins(
|
|
322
|
+
node,
|
|
323
|
+
known,
|
|
324
|
+
lambda arg: inference(arg, known, declared),
|
|
325
|
+
) and (
|
|
326
|
+
_returned_certainly(func, known)
|
|
327
|
+
or decided.decides(func, known)
|
|
328
|
+
or dotted(func) in known.names.casts
|
|
329
|
+
or stdlib.resolved(func, known.names.stdlib) in stdlib.KNOWN
|
|
330
|
+
or dotted(func) in known.names.installed
|
|
331
|
+
or opened(node, known) is not None
|
|
332
|
+
or walks.walked(node, known, lambda arg: inference(arg, known, declared)) is not None
|
|
333
|
+
or certain_method(node, known, declared)
|
|
334
|
+
or _overloaded_method(node, known, declared)
|
|
335
|
+
or called.result(node, known, declared, lambda arg: inference(arg, known, declared)) is not None
|
|
336
|
+
or shapes.partly(node, known, lambda arg: inference(arg, known, declared)) is not None
|
|
337
|
+
# A standard-library coroutine's: awaited, what it declares.
|
|
338
|
+
or awaited(ast.Await(node), known, lambda arg: inference(arg, known, declared)) is not None
|
|
339
|
+
):
|
|
340
|
+
return False
|
|
341
|
+
case ast.Call(func=func):
|
|
342
|
+
return dotted(func) not in known.calls
|
|
343
|
+
case ast.Name(id=name):
|
|
344
|
+
return name in guesses
|
|
345
|
+
case ast.Attribute():
|
|
346
|
+
return _read(node) in guesses or _assigned_origins(node, known, declared) is not None
|
|
347
|
+
case _:
|
|
348
|
+
return False
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _read(node: ast.AST) -> str:
|
|
352
|
+
"""Name what a node reads that a scope types by name: a local, or a fixture's `request.param`.
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
The key its type is under (see `Typed`); `""` for any other node.
|
|
356
|
+
|
|
357
|
+
"""
|
|
358
|
+
name: str
|
|
359
|
+
match node:
|
|
360
|
+
case ast.Name(id=name):
|
|
361
|
+
return name
|
|
362
|
+
case ast.Attribute(value=ast.Name(id=name), attr="param"):
|
|
363
|
+
return f"{name}{PARAM}"
|
|
364
|
+
case _:
|
|
365
|
+
return ""
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _returned_certainly(func: ast.expr, known: Known) -> bool:
|
|
369
|
+
"""Check whether `func` is an unannotated function (own or imported) certain of its `return`s' type.
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
Whether it is.
|
|
373
|
+
|
|
374
|
+
"""
|
|
375
|
+
callee: str | None = dotted(func)
|
|
376
|
+
return callee in known.returned.calls and callee not in known.returned.guesses
|
|
Binary file
|