python-constricter 0.4.1__cp314-cp314-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__.cp314-win_amd64.pyd +0 -0
- constricter/fix/__init__.py +2 -0
- constricter/fix/core/__init__.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/__init__.py +2 -0
- constricter/fix/core/asked.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/asked.py +56 -0
- constricter/fix/core/fixes.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/fixes.py +317 -0
- constricter/fix/core/imports.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/imports.py +580 -0
- constricter/fix/core/inherited.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/inherited.py +311 -0
- constricter/fix/core/known.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/known.py +730 -0
- constricter/fix/core/signatures.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/signatures.py +132 -0
- constricter/fix/index/__init__.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/__init__.py +2 -0
- constricter/fix/index/awaits.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/awaits.py +90 -0
- constricter/fix/index/beyond.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/beyond.py +149 -0
- constricter/fix/index/callers.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/callers.py +155 -0
- constricter/fix/index/crossed.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/crossed.py +327 -0
- constricter/fix/index/decorated.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/decorated.py +140 -0
- constricter/fix/index/fixtures.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/fixtures.py +176 -0
- constricter/fix/index/linked.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/linked.py +142 -0
- constricter/fix/index/loose.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/loose.py +103 -0
- constricter/fix/index/managed.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/managed.py +63 -0
- constricter/fix/index/modules.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/modules.py +741 -0
- constricter/fix/index/offers.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/offers.py +252 -0
- constricter/fix/index/order.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/order.py +155 -0
- constricter/fix/index/origins.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/origins.py +238 -0
- constricter/fix/index/own_methods.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/own_methods.py +91 -0
- constricter/fix/index/own_overloads.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/own_overloads.py +70 -0
- constricter/fix/index/own_types.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/own_types.py +330 -0
- constricter/fix/index/params.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/params.py +81 -0
- constricter/fix/index/plain.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/plain.py +221 -0
- constricter/fix/index/project.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/project.py +797 -0
- constricter/fix/index/sides.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/sides.py +160 -0
- constricter/fix/index/strings.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/strings.py +46 -0
- constricter/fix/index/stubs/__init__.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/__init__.py +2 -0
- constricter/fix/index/stubs/atoms.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/atoms.py +425 -0
- constricter/fix/index/stubs/chained.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/chained.py +69 -0
- constricter/fix/index/stubs/classnames.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/classnames.py +124 -0
- constricter/fix/index/stubs/declared.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/declared.py +508 -0
- constricter/fix/index/stubs/installed.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/installed.py +425 -0
- constricter/fix/index/stubs/names.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/names.py +200 -0
- constricter/fix/index/stubs/read.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/read.py +70 -0
- constricter/fix/index/stubs/signatures.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/signatures.py +656 -0
- constricter/fix/index/stubs/stubbed.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/stubbed.py +208 -0
- constricter/fix/index/taken.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/taken.py +45 -0
- constricter/fix/index/tuples.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/tuples.py +112 -0
- constricter/fix/libraries/__init__.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/__init__.py +2 -0
- constricter/fix/libraries/library.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/library.py +420 -0
- constricter/fix/libraries/opened.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/opened.py +147 -0
- constricter/fix/libraries/overloads.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/overloads.py +960 -0
- constricter/fix/libraries/receivers.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/receivers.py +274 -0
- constricter/fix/libraries/stdlib.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/stdlib.py +752 -0
- constricter/fix/libraries/walks.cp314-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__.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/__init__.py +2 -0
- constricter/fix/values/aliased.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/aliased.py +195 -0
- constricter/fix/values/bodies.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/bodies.py +76 -0
- constricter/fix/values/callables.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/callables.py +323 -0
- constricter/fix/values/called.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/called.py +194 -0
- constricter/fix/values/classvars.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/classvars.py +629 -0
- constricter/fix/values/decided.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/decided.py +429 -0
- constricter/fix/values/displays.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/displays.py +101 -0
- constricter/fix/values/doubts.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/doubts.py +677 -0
- constricter/fix/values/entered.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/entered.py +332 -0
- constricter/fix/values/fills.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/fills.py +446 -0
- constricter/fix/values/guesses.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/guesses.py +376 -0
- constricter/fix/values/hinted.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/hinted.py +811 -0
- constricter/fix/values/inference.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/inference.py +698 -0
- constricter/fix/values/loops.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/loops.py +335 -0
- constricter/fix/values/members.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/members.py +578 -0
- constricter/fix/values/narrowed.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/narrowed.py +220 -0
- constricter/fix/values/operated.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/operated.py +202 -0
- constricter/fix/values/ordered.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/ordered.py +74 -0
- constricter/fix/values/returned.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/returned.py +479 -0
- constricter/fix/values/returns.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/returns.py +237 -0
- constricter/fix/values/shapes.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/shapes.py +654 -0
- constricter/fix/values/stored.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/stored.py +564 -0
- constricter/fix/values/targets.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/targets.py +419 -0
- constricter/fix/values/unchecked.cp314-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__.cp314-win_amd64.pyd +0 -0
- constricter/rules/__init__.py +2 -0
- constricter/rules/annotations.cp314-win_amd64.pyd +0 -0
- constricter/rules/annotations.py +296 -0
- constricter/rules/binding.cp314-win_amd64.pyd +0 -0
- constricter/rules/binding.py +652 -0
- constricter/rules/calls.cp314-win_amd64.pyd +0 -0
- constricter/rules/calls.py +460 -0
- constricter/rules/checker.cp314-win_amd64.pyd +0 -0
- constricter/rules/checker.py +536 -0
- constricter/rules/declarations.cp314-win_amd64.pyd +0 -0
- constricter/rules/declarations.py +730 -0
- constricter/rules/decorators.cp314-win_amd64.pyd +0 -0
- constricter/rules/decorators.py +351 -0
- constricter/rules/flow.cp314-win_amd64.pyd +0 -0
- constricter/rules/flow.py +502 -0
- constricter/rules/keys.cp314-win_amd64.pyd +0 -0
- constricter/rules/keys.py +79 -0
- constricter/rules/late.cp314-win_amd64.pyd +0 -0
- constricter/rules/late.py +969 -0
- constricter/rules/narrowing.cp314-win_amd64.pyd +0 -0
- constricter/rules/narrowing.py +98 -0
- constricter/rules/parsed.cp314-win_amd64.pyd +0 -0
- constricter/rules/parsed.py +136 -0
- constricter/rules/quoted.cp314-win_amd64.pyd +0 -0
- constricter/rules/quoted.py +141 -0
- constricter/rules/rebinding.cp314-win_amd64.pyd +0 -0
- constricter/rules/rebinding.py +169 -0
- constricter/rules/recorded.cp314-win_amd64.pyd +0 -0
- constricter/rules/recorded.py +168 -0
- constricter/rules/redundant.cp314-win_amd64.pyd +0 -0
- constricter/rules/redundant.py +160 -0
- constricter/rules/scope.cp314-win_amd64.pyd +0 -0
- constricter/rules/scope.py +802 -0
- constricter/rules/scoped.cp314-win_amd64.pyd +0 -0
- constricter/rules/scoped.py +232 -0
- constricter/rules/scoping.cp314-win_amd64.pyd +0 -0
- constricter/rules/scoping.py +346 -0
- constricter/rules/settings.cp314-win_amd64.pyd +0 -0
- constricter/rules/settings.py +189 -0
- constricter/rules/syntax.cp314-win_amd64.pyd +0 -0
- constricter/rules/syntax.py +472 -0
- constricter/rules/tables.cp314-win_amd64.pyd +0 -0
- constricter/rules/tables.py +169 -0
- constricter/rules/walked.cp314-win_amd64.pyd +0 -0
- constricter/rules/walked.py +127 -0
- constricter/rules/widened.cp314-win_amd64.pyd +0 -0
- constricter/rules/widened.py +322 -0
- constricter/trace.py +374 -0
- e45ce06b2fdd5c43f782__mypyc.cp314-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,508 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""What an installed package's stub declares that a call's arguments may decide the type of.
|
|
3
|
+
|
|
4
|
+
`declarations` reads, from a module's top level: each function whose arguments decide its return
|
|
5
|
+
(`@overload`s, or a return naming a type variable) with its signatures as written; type aliases
|
|
6
|
+
(`X: TypeAlias = ...`, `type X[T] = ...`, `X = A | B`); type variables' bounds and constraints; and
|
|
7
|
+
protocols' members. All as source text, for `constricter.fix.index.stubs.signatures` to resolve across modules
|
|
8
|
+
when a call needs them (`Module` keeps them, pickled into the installed modules' cache).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import ast
|
|
12
|
+
from collections.abc import Collection, Iterator, Mapping, Sequence
|
|
13
|
+
from typing import Final, NamedTuple, TypeAlias, cast
|
|
14
|
+
|
|
15
|
+
from constricter.empty import Empty
|
|
16
|
+
from constricter.rules.annotations import node_name
|
|
17
|
+
from constricter.rules.declarations import defined_type_vars
|
|
18
|
+
|
|
19
|
+
# A parameter: its name, kind (`p` positional only, `e` either, `k` keyword only, `a` `*args`,
|
|
20
|
+
# `w` `**kwargs`), whether it has a default, and its annotation's text (`None`: unannotated).
|
|
21
|
+
Param: TypeAlias = tuple[str, str, bool, str | None]
|
|
22
|
+
_OVERLOAD: Final = "overload"
|
|
23
|
+
_TYPE_VARIABLES: Final = frozenset({"TypeVar", "ParamSpec", "TypeVarTuple"})
|
|
24
|
+
_TYPE_VAR: Final = "TypeVar"
|
|
25
|
+
_TYPE_ALIAS: Final = "TypeAlias"
|
|
26
|
+
_PROTOCOL: Final = "Protocol"
|
|
27
|
+
_TYPE_CHECKING: Final = "TYPE_CHECKING"
|
|
28
|
+
_PROPERTIES: Final = frozenset({"property", "cached_property"})
|
|
29
|
+
_BOUND: Final = "bound"
|
|
30
|
+
_STAR: Final = "*"
|
|
31
|
+
_SELF: Final = "Self" # a method returning its instance's own type
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Signature(NamedTuple):
|
|
35
|
+
"""One signature: its parameters, its return's text (`None`: unannotated), and its type parameters.
|
|
36
|
+
|
|
37
|
+
`type_params`: its own (`def f[T: int]`), each with its bound's text, or `None`; a `ParamSpec`
|
|
38
|
+
or `TypeVarTuple` has `"*"` for one.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
params: tuple[Param, ...]
|
|
42
|
+
returns: str | None
|
|
43
|
+
type_params: tuple[tuple[str, str | None], ...] = ()
|
|
44
|
+
self_type: str | None = None # a method's `self` annotation (not `Self`): only such instances have it
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Class(NamedTuple):
|
|
48
|
+
"""A class: its type parameters (each with its bound's text, or `None`), and its methods' signatures.
|
|
49
|
+
|
|
50
|
+
`bases`: its bases as written. `methods`: those whose arguments or instance decide their return:
|
|
51
|
+
overloaded, naming a type variable, or any at all for a generic class (its parameters bind them);
|
|
52
|
+
each signature without `self`. `attributes`: its body's annotated names, each one's annotation.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
params: tuple[tuple[str, str | None], ...]
|
|
56
|
+
bases: tuple[str, ...]
|
|
57
|
+
methods: Mapping[str, tuple[Signature, ...]]
|
|
58
|
+
attributes: Mapping[str, str] = Empty()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Alias(NamedTuple):
|
|
62
|
+
"""A type alias: its value's text, and its own type parameters (`type X[T] = ...`'s; else none)."""
|
|
63
|
+
|
|
64
|
+
value: str
|
|
65
|
+
params: tuple[str, ...] = ()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Variable(NamedTuple):
|
|
69
|
+
"""A type variable: its bound's text, or its constraints joined with `|`; `constrained` says which."""
|
|
70
|
+
|
|
71
|
+
bound: str | None
|
|
72
|
+
constrained: bool = False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class Protocol(NamedTuple):
|
|
76
|
+
"""A protocol class: the members an instance needs, and whether one is a property."""
|
|
77
|
+
|
|
78
|
+
members: frozenset[str]
|
|
79
|
+
properties: bool
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Declarations(NamedTuple):
|
|
83
|
+
"""What `declarations` reads from a module (see the module docstring)."""
|
|
84
|
+
|
|
85
|
+
signatures: Mapping[str, tuple[Signature, ...]]
|
|
86
|
+
aliases: Mapping[str, Alias]
|
|
87
|
+
variables: Mapping[str, Variable]
|
|
88
|
+
protocols: Mapping[str, Protocol]
|
|
89
|
+
# What another module may import from it, as type checkers read a typed package's interface:
|
|
90
|
+
# its `__all__`, else its public definitions and redundant-alias imports (`from m import x as x`);
|
|
91
|
+
# `None` where a `*` import leaves that open.
|
|
92
|
+
exports: frozenset[str] | None = None
|
|
93
|
+
# Classes generic only through a subscripted base (`class float64(floating[_64Bit])`), each with
|
|
94
|
+
# the names in its bases' subscripts: generic if one is a type variable (see `installed`).
|
|
95
|
+
bases: Mapping[str, frozenset[str]] = Empty()
|
|
96
|
+
classes: Mapping[str, Class] = Empty() # every class, for its methods
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def overloads(tree: ast.Module) -> dict[str, tuple[Signature, ...]]:
|
|
100
|
+
"""Read a checked module's top-level functions defined with `@overload`, as `declarations` reads a stub's.
|
|
101
|
+
|
|
102
|
+
And its top-level classes' methods, each as `Class.method`, less its `self`: of a class
|
|
103
|
+
defined once.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Each one's overloads' signatures, as written.
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
found: dict[str, tuple[Signature, ...]] = _overloads(tree.body)
|
|
110
|
+
names: list[str] = [stmt.name for stmt in tree.body if isinstance(stmt, ast.ClassDef)]
|
|
111
|
+
stmt: ast.stmt
|
|
112
|
+
for stmt in tree.body:
|
|
113
|
+
if isinstance(stmt, ast.ClassDef) and names.count(stmt.name) == 1:
|
|
114
|
+
found.update(
|
|
115
|
+
(f"{stmt.name}.{name}", tuple(_unbound(each) for each in written))
|
|
116
|
+
for name, written in _overloads(stmt.body).items()
|
|
117
|
+
if all(each.params for each in written)
|
|
118
|
+
)
|
|
119
|
+
return found
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _overloads(body: Sequence[ast.stmt]) -> dict[str, tuple[Signature, ...]]:
|
|
123
|
+
signatures: dict[str, list[Signature]] = {}
|
|
124
|
+
overloaded: set[str] = set()
|
|
125
|
+
stmt: ast.stmt
|
|
126
|
+
for stmt in body:
|
|
127
|
+
if isinstance(stmt, ast.FunctionDef):
|
|
128
|
+
_signature(stmt, signatures, overloaded, frozenset())
|
|
129
|
+
return {name: tuple(signatures[name]) for name in sorted(overloaded)}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def kinds(tree: ast.Module, aliases: Collection[str]) -> Declarations | None:
|
|
133
|
+
"""Read what a checked module's names stand for in a signature: an alias, a type variable, a protocol.
|
|
134
|
+
|
|
135
|
+
`aliases`: the names it binds once as a type alias (see `modules`): no other assignment is one.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Them, as `declarations` reads a stub's, and nothing else of its; `None` for a module with none.
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
found: dict[str, Alias] = {}
|
|
142
|
+
variables: dict[str, Variable] = {}
|
|
143
|
+
protocols: dict[str, Protocol] = {}
|
|
144
|
+
stmt: ast.stmt
|
|
145
|
+
for stmt in tree.body:
|
|
146
|
+
if isinstance(stmt, ast.ClassDef):
|
|
147
|
+
if any(node_name(base) == _PROTOCOL for base in _bases(stmt)):
|
|
148
|
+
protocols[stmt.name] = _protocol(stmt)
|
|
149
|
+
else:
|
|
150
|
+
_assignment(stmt, found, variables)
|
|
151
|
+
kept: dict[str, Alias] = {name: alias for name, alias in found.items() if name in aliases}
|
|
152
|
+
return Declarations({}, kept, variables, protocols) if kept or variables or protocols else None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def declarations(tree: ast.Module) -> Declarations:
|
|
156
|
+
"""Read a stub's declarations (see the module docstring).
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Them.
|
|
160
|
+
|
|
161
|
+
"""
|
|
162
|
+
type_vars: frozenset[str] = defined_type_vars(tree)
|
|
163
|
+
signatures: dict[str, list[Signature]] = {}
|
|
164
|
+
overloaded: set[str] = set()
|
|
165
|
+
aliases: dict[str, Alias] = {}
|
|
166
|
+
variables: dict[str, Variable] = {}
|
|
167
|
+
protocols: dict[str, Protocol] = {}
|
|
168
|
+
bases: dict[str, frozenset[str]] = {}
|
|
169
|
+
classes: dict[str, Class] = {}
|
|
170
|
+
names: frozenset[str] | None
|
|
171
|
+
stmt: ast.stmt
|
|
172
|
+
for stmt in (*tree.body, *_checked_classes(tree.body)):
|
|
173
|
+
match stmt:
|
|
174
|
+
case ast.FunctionDef():
|
|
175
|
+
_signature(stmt, signatures, overloaded, type_vars)
|
|
176
|
+
case ast.ClassDef():
|
|
177
|
+
if any(node_name(base) == _PROTOCOL for base in _bases(stmt)):
|
|
178
|
+
protocols[stmt.name] = _protocol(stmt)
|
|
179
|
+
if (names := _subscripted(stmt)) is not None:
|
|
180
|
+
bases[stmt.name] = names
|
|
181
|
+
classes[stmt.name] = _class(stmt, type_vars, variables)
|
|
182
|
+
case _:
|
|
183
|
+
_assignment(stmt, aliases, variables)
|
|
184
|
+
return Declarations(
|
|
185
|
+
{name: tuple(found) for name, found in signatures.items() if found},
|
|
186
|
+
aliases,
|
|
187
|
+
variables,
|
|
188
|
+
protocols,
|
|
189
|
+
_exports(tree),
|
|
190
|
+
bases,
|
|
191
|
+
classes,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _checked_classes(body: list[ast.stmt]) -> Iterator[ast.ClassDef]:
|
|
196
|
+
"""Find the classes a module defines under a top-level `if` a type checker takes the first arm of.
|
|
197
|
+
|
|
198
|
+
`if sys.version_info >= (3, 11) or TYPE_CHECKING:`, by the `TYPE_CHECKING` its test names.
|
|
199
|
+
|
|
200
|
+
Yields:
|
|
201
|
+
Each class of that arm.
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
stmt: ast.stmt
|
|
205
|
+
for stmt in body:
|
|
206
|
+
if isinstance(stmt, ast.If) and any(
|
|
207
|
+
node_name(node) == _TYPE_CHECKING for node in ast.walk(stmt.test)
|
|
208
|
+
):
|
|
209
|
+
yield from (each for each in stmt.body if isinstance(each, ast.ClassDef))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _class(node: ast.ClassDef, type_vars: frozenset[str], variables: Mapping[str, Variable]) -> Class:
|
|
213
|
+
"""Read a class's type parameters and its methods (see `Class`).
|
|
214
|
+
|
|
215
|
+
Its own (`class C[T]`), else a `Generic[...]` or `Protocol[...]` base's, else the type variables
|
|
216
|
+
its bases' subscripts name, in order.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
It.
|
|
220
|
+
|
|
221
|
+
"""
|
|
222
|
+
own: tuple[tuple[str, str | None], ...] = tuple(_type_params(node))
|
|
223
|
+
subscripts: list[ast.Subscript] = [base for base in node.bases if isinstance(base, ast.Subscript)]
|
|
224
|
+
generic: list[ast.Subscript] = [
|
|
225
|
+
base for base in subscripts if node_name(base.value) in {"Generic", _PROTOCOL}
|
|
226
|
+
]
|
|
227
|
+
known: frozenset[str] = type_vars.union(variables.keys())
|
|
228
|
+
names: list[str] = list(
|
|
229
|
+
dict.fromkeys(
|
|
230
|
+
found.id
|
|
231
|
+
for base in (generic or subscripts)
|
|
232
|
+
for found in ast.walk(base.slice)
|
|
233
|
+
if isinstance(found, ast.Name) and (generic or found.id in known)
|
|
234
|
+
),
|
|
235
|
+
)
|
|
236
|
+
params: tuple[tuple[str, str | None], ...] = own or tuple(
|
|
237
|
+
(name, None if name not in variables or variables[name].constrained else variables[name].bound)
|
|
238
|
+
for name in names
|
|
239
|
+
)
|
|
240
|
+
signatures: dict[str, list[Signature]] = {}
|
|
241
|
+
overloaded: set[str] = set()
|
|
242
|
+
stmt: ast.stmt
|
|
243
|
+
for stmt in node.body:
|
|
244
|
+
if isinstance(stmt, ast.FunctionDef) and (stmt.args.posonlyargs or stmt.args.args):
|
|
245
|
+
_signature(
|
|
246
|
+
stmt,
|
|
247
|
+
signatures,
|
|
248
|
+
overloaded,
|
|
249
|
+
known | {_SELF, *(name for name, _ in params)},
|
|
250
|
+
every=bool(params),
|
|
251
|
+
)
|
|
252
|
+
return Class(
|
|
253
|
+
params,
|
|
254
|
+
tuple(ast.unparse(base) for base in node.bases),
|
|
255
|
+
{name: tuple(_unbound(each) for each in found) for name, found in signatures.items() if found},
|
|
256
|
+
{
|
|
257
|
+
each.target.id: ast.unparse(each.annotation)
|
|
258
|
+
for each in node.body
|
|
259
|
+
if isinstance(each, ast.AnnAssign) and isinstance(each.target, ast.Name)
|
|
260
|
+
},
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _unbound(signature: Signature) -> Signature:
|
|
265
|
+
"""Drop a method's `self` from its signature, noting whether it's annotated (not as `Self`).
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
The signature.
|
|
269
|
+
|
|
270
|
+
"""
|
|
271
|
+
annotation: str | None = signature.params[0][3]
|
|
272
|
+
return signature._replace(
|
|
273
|
+
params=signature.params[1:],
|
|
274
|
+
self_type=None if annotation is None or annotation.rpartition(".")[2] == _SELF else annotation,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _subscripted(node: ast.ClassDef) -> frozenset[str] | None:
|
|
279
|
+
"""Name what a class's subscripted bases pass their type parameters, where that alone makes it generic.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Those names; `None` for a class with type parameters of its own, a `Generic[...]` or
|
|
283
|
+
`Protocol[...]` base, or no subscripted base.
|
|
284
|
+
|
|
285
|
+
"""
|
|
286
|
+
subscripts: list[ast.Subscript] = [base for base in node.bases if isinstance(base, ast.Subscript)]
|
|
287
|
+
if (
|
|
288
|
+
not subscripts
|
|
289
|
+
or cast("object", getattr(node, "type_params", ()))
|
|
290
|
+
or any(node_name(base.value) in {"Generic", _PROTOCOL} for base in subscripts)
|
|
291
|
+
):
|
|
292
|
+
return None
|
|
293
|
+
return frozenset(
|
|
294
|
+
found.id for base in subscripts for found in ast.walk(base.slice) if isinstance(found, ast.Name)
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _exports(tree: ast.Module) -> frozenset[str] | None:
|
|
299
|
+
"""Name what a typed package's module exports (see `Declarations.exports`).
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Them, or `None` if a `*` import (without an `__all__`) makes that open.
|
|
303
|
+
|
|
304
|
+
"""
|
|
305
|
+
listed: list[str] | None = None
|
|
306
|
+
names: set[str] = set()
|
|
307
|
+
starred: bool = False
|
|
308
|
+
stmt: ast.stmt
|
|
309
|
+
value: ast.expr
|
|
310
|
+
for stmt in tree.body:
|
|
311
|
+
match stmt:
|
|
312
|
+
case ast.Assign(targets=[ast.Name(id="__all__")], value=value):
|
|
313
|
+
listed = _strings(value)
|
|
314
|
+
case ast.AugAssign(target=ast.Name(id="__all__"), op=ast.Add(), value=value):
|
|
315
|
+
listed = [*(listed or []), *_strings(value)]
|
|
316
|
+
case ast.Import() | ast.ImportFrom():
|
|
317
|
+
starred |= any(alias.name == _STAR for alias in stmt.names)
|
|
318
|
+
names.update(alias.name for alias in stmt.names if alias.asname == alias.name)
|
|
319
|
+
case ast.FunctionDef() | ast.AsyncFunctionDef() | ast.ClassDef():
|
|
320
|
+
names.add(stmt.name)
|
|
321
|
+
case ast.Assign() | ast.AnnAssign():
|
|
322
|
+
targets: list[ast.expr] = stmt.targets if isinstance(stmt, ast.Assign) else [stmt.target]
|
|
323
|
+
names.update(target.id for target in targets if isinstance(target, ast.Name))
|
|
324
|
+
case _ if type(stmt).__name__ == _TYPE_ALIAS:
|
|
325
|
+
names.add(_type_alias(stmt)[0])
|
|
326
|
+
case _:
|
|
327
|
+
pass
|
|
328
|
+
if listed is not None:
|
|
329
|
+
return frozenset(listed)
|
|
330
|
+
return None if starred else frozenset(name for name in names if not name.startswith("_"))
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _strings(value: ast.expr) -> list[str]:
|
|
334
|
+
elements: list[ast.expr] = value.elts if isinstance(value, ast.List | ast.Tuple) else []
|
|
335
|
+
return [e.value for e in elements if isinstance(e, ast.Constant) and isinstance(e.value, str)]
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _signature(
|
|
339
|
+
node: ast.FunctionDef,
|
|
340
|
+
signatures: dict[str, list[Signature]],
|
|
341
|
+
overloaded: set[str],
|
|
342
|
+
type_vars: frozenset[str],
|
|
343
|
+
*,
|
|
344
|
+
every: bool = False,
|
|
345
|
+
) -> None:
|
|
346
|
+
"""Add a function's signature: an `@overload` to those before it; a generic one as its only one.
|
|
347
|
+
|
|
348
|
+
With `every` (a generic class's methods), any undecorated one with a return annotation too.
|
|
349
|
+
"""
|
|
350
|
+
overload: bool = any(node_name(decorator) == _OVERLOAD for decorator in node.decorator_list)
|
|
351
|
+
if node.name in overloaded:
|
|
352
|
+
if overload:
|
|
353
|
+
signatures[node.name].append(_read(node))
|
|
354
|
+
return # the implementation after the overloads, which callers don't see
|
|
355
|
+
signature: Signature = _read(node)
|
|
356
|
+
if overload:
|
|
357
|
+
overloaded.add(node.name)
|
|
358
|
+
signatures[node.name] = [signature]
|
|
359
|
+
elif not node.decorator_list and (
|
|
360
|
+
signature.type_params or _mentions(node.returns, type_vars) or (every and node.returns is not None)
|
|
361
|
+
):
|
|
362
|
+
signatures[node.name] = [signature]
|
|
363
|
+
else:
|
|
364
|
+
signatures[node.name] = [] # defined again later: whichever is last is the one
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _read(node: ast.FunctionDef) -> Signature:
|
|
368
|
+
"""Read one signature as written.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
It.
|
|
372
|
+
|
|
373
|
+
"""
|
|
374
|
+
args: ast.arguments = node.args
|
|
375
|
+
positional: list[ast.arg] = [*args.posonlyargs, *args.args]
|
|
376
|
+
first_default: int = len(positional) - len(args.defaults)
|
|
377
|
+
params: list[Param] = [
|
|
378
|
+
(arg.arg, "p" if at < len(args.posonlyargs) else "e", at >= first_default, _text(arg.annotation))
|
|
379
|
+
for at, arg in enumerate(positional)
|
|
380
|
+
]
|
|
381
|
+
if args.vararg is not None:
|
|
382
|
+
params.append((args.vararg.arg, "a", True, _text(args.vararg.annotation)))
|
|
383
|
+
params.extend(
|
|
384
|
+
(arg.arg, "k", default is not None, _text(arg.annotation))
|
|
385
|
+
for arg, default in zip(args.kwonlyargs, args.kw_defaults, strict=True)
|
|
386
|
+
)
|
|
387
|
+
if args.kwarg is not None:
|
|
388
|
+
params.append((args.kwarg.arg, "w", True, _text(args.kwarg.annotation)))
|
|
389
|
+
return Signature(tuple(params), _text(node.returns), tuple(_type_params(node)))
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _type_params(node: ast.AST) -> Iterator[tuple[str, str | None]]:
|
|
393
|
+
"""Read a definition's own type parameters (Python 3.12+'s `def f[T: int]`, `type X[T] = ...`).
|
|
394
|
+
|
|
395
|
+
Yields:
|
|
396
|
+
Each one's name and bound (`"*"` for a `ParamSpec` or `TypeVarTuple`).
|
|
397
|
+
|
|
398
|
+
"""
|
|
399
|
+
param: ast.AST
|
|
400
|
+
for param in cast("list[ast.AST]", getattr(node, "type_params", [])):
|
|
401
|
+
name: str = cast("str", getattr(param, "name", ""))
|
|
402
|
+
if type(param).__name__ == _TYPE_VAR:
|
|
403
|
+
yield name, _text(cast("ast.expr | None", getattr(param, _BOUND, None)))
|
|
404
|
+
else:
|
|
405
|
+
yield name, "*"
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _mentions(annotation: ast.expr | None, type_vars: frozenset[str]) -> bool:
|
|
409
|
+
return annotation is not None and any(
|
|
410
|
+
isinstance(node, ast.Name) and node.id in type_vars for node in ast.walk(annotation)
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _text(annotation: ast.expr | None) -> str | None:
|
|
415
|
+
return None if annotation is None else ast.unparse(annotation)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _bases(node: ast.ClassDef) -> Iterator[ast.expr]:
|
|
419
|
+
base: ast.expr
|
|
420
|
+
for base in node.bases:
|
|
421
|
+
yield base.value if isinstance(base, ast.Subscript) else base
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _protocol(node: ast.ClassDef) -> Protocol:
|
|
425
|
+
"""Read a protocol's members: what its body defines or declares.
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
It.
|
|
429
|
+
|
|
430
|
+
"""
|
|
431
|
+
members: set[str] = set()
|
|
432
|
+
properties: bool = False
|
|
433
|
+
stmt: ast.stmt
|
|
434
|
+
name: str
|
|
435
|
+
for stmt in node.body:
|
|
436
|
+
match stmt:
|
|
437
|
+
case ast.FunctionDef() | ast.AsyncFunctionDef():
|
|
438
|
+
members.add(stmt.name)
|
|
439
|
+
properties |= any(node_name(d) in _PROPERTIES for d in stmt.decorator_list)
|
|
440
|
+
case ast.AnnAssign(target=ast.Name(id=name)):
|
|
441
|
+
members.add(name)
|
|
442
|
+
properties = True # an instance attribute: a class object's is its own
|
|
443
|
+
case _:
|
|
444
|
+
pass
|
|
445
|
+
return Protocol(
|
|
446
|
+
frozenset(members - {"__init__", "__new__", "__slots__", "__class_getitem__"}),
|
|
447
|
+
properties,
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _assignment(stmt: ast.stmt, aliases: dict[str, Alias], variables: dict[str, Variable]) -> None:
|
|
452
|
+
"""Read a type alias or a type variable a top-level statement declares, if it declares one."""
|
|
453
|
+
func: ast.expr
|
|
454
|
+
args: list[ast.expr]
|
|
455
|
+
keywords: list[ast.keyword]
|
|
456
|
+
annotation: ast.expr
|
|
457
|
+
name: str
|
|
458
|
+
value: ast.expr
|
|
459
|
+
found: Alias
|
|
460
|
+
match stmt:
|
|
461
|
+
case ast.Assign(
|
|
462
|
+
targets=[ast.Name(id=name)],
|
|
463
|
+
value=ast.Call(func=func, args=args, keywords=keywords),
|
|
464
|
+
) if node_name(func).removeprefix("_") in _TYPE_VARIABLES:
|
|
465
|
+
bound: ast.expr | None = next((k.value for k in keywords if k.arg == _BOUND), None)
|
|
466
|
+
constraints: list[str] = [ast.unparse(arg) for arg in args[1:]]
|
|
467
|
+
variables[name] = (
|
|
468
|
+
Variable(" | ".join(constraints), constrained=True)
|
|
469
|
+
if constraints
|
|
470
|
+
else Variable(_text(bound) if node_name(func).removeprefix("_") == _TYPE_VAR else "*")
|
|
471
|
+
)
|
|
472
|
+
case ast.AnnAssign(target=ast.Name(id=name), annotation=annotation, value=ast.expr() as value) if (
|
|
473
|
+
node_name(annotation) == _TYPE_ALIAS
|
|
474
|
+
):
|
|
475
|
+
aliases[name] = Alias(ast.unparse(value))
|
|
476
|
+
case ast.Assign(targets=[ast.Name(id=name)], value=value) if _type_expression(value):
|
|
477
|
+
aliases[name] = Alias(ast.unparse(value))
|
|
478
|
+
case _ if type(stmt).__name__ == _TYPE_ALIAS: # `type X[T] = ...` (3.12+)
|
|
479
|
+
name, found = _type_alias(stmt)
|
|
480
|
+
aliases[name] = found
|
|
481
|
+
case _:
|
|
482
|
+
pass
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _type_alias(stmt: ast.stmt) -> tuple[str, Alias]:
|
|
486
|
+
"""Read Python 3.12+'s `type X[T] = ...` (an `ast.TypeAlias`, which 3.11's `ast` doesn't have).
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
Its name, and it.
|
|
490
|
+
|
|
491
|
+
"""
|
|
492
|
+
named: ast.Name = cast("ast.Name", getattr(stmt, "name", None))
|
|
493
|
+
value: ast.expr = cast("ast.expr", getattr(stmt, "value", None))
|
|
494
|
+
return named.id, Alias(ast.unparse(value), tuple(param for param, _ in _type_params(stmt)))
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def _type_expression(value: ast.expr) -> bool:
|
|
498
|
+
"""Check whether an assignment's value may be a type (`A | B`, `list[int]`, `pkg.Class`), not a value.
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
Whether it may.
|
|
502
|
+
|
|
503
|
+
"""
|
|
504
|
+
match value:
|
|
505
|
+
case ast.BinOp(op=ast.BitOr()) | ast.Subscript() | ast.Name() | ast.Attribute():
|
|
506
|
+
return True
|
|
507
|
+
case _:
|
|
508
|
+
return False
|
|
Binary file
|