python-constricter 0.4.4__tar.gz → 0.4.5__tar.gz
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.
- {python_constricter-0.4.4 → python_constricter-0.4.5}/PKG-INFO +1 -1
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/_version.py +1 -1
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/rounds.py +34 -5
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/core/imports.py +32 -4
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/core/known.py +76 -10
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/across.py +33 -5
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/crossed.py +92 -9
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/linked.py +10 -5
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/modules.py +273 -122
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/offers.py +76 -2
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/origins.py +28 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/own_methods.py +56 -8
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/own_overloads.py +25 -6
- python_constricter-0.4.5/constricter/fix/index/own_stubs.py +95 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/own_types.py +21 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/plain.py +136 -24
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/project.py +85 -2
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/spelling.py +17 -21
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/stubs/atoms.py +6 -2
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/stubs/classnames.py +6 -2
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/stubs/declared.py +55 -4
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/stubs/installed.py +168 -6
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/stubs/signatures.py +52 -8
- python_constricter-0.4.5/constricter/fix/index/used.py +130 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/libraries/stdlib.py +28 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/attributes.json +151 -56
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/awaited.json +1 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/classes.json +4 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/method_overloads.json +7 -6
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/method_signatures.json +14 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/methods.json +68 -58
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/returns.json +3 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/source.json +1 -1
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/classvars.py +333 -15
- python_constricter-0.4.5/constricter/fix/values/documented.py +440 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/doubts.py +63 -7
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/guesses.py +81 -4
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/inference.py +67 -6
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/members.py +96 -8
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/reads.py +15 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/refused.py +2 -1
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/returned.py +110 -13
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/returns.py +133 -4
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/tables.py +64 -17
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/offences.py +4 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/attributes.py +17 -1
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/checker.py +36 -4
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/scope.py +49 -14
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/scoping.py +6 -3
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/settings.py +6 -4
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/source/annotations.py +32 -8
- python_constricter-0.4.5/constricter/source/concrete.py +247 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/source/declarations.py +187 -86
- python_constricter-0.4.5/constricter/source/inert.py +194 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/source/syntax.py +63 -0
- python_constricter-0.4.5/constricter/source/usable.py +63 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/traced.py +33 -7
- {python_constricter-0.4.4 → python_constricter-0.4.5}/pyproject.toml +1 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/stdlib_tables/generate.py +2 -1
- {python_constricter-0.4.4 → python_constricter-0.4.5}/stdlib_tables/partial.json +2 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/stdlib_tables/reading.py +7 -4
- {python_constricter-0.4.4 → python_constricter-0.4.5}/.gitignore +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/LICENSE.md +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/README.md +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/__init__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/__main__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/__init__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/baseline.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/collecting.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/command.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/config.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/explain.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/lsp/__init__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/lsp/connection.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/lsp/edits.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/lsp/guard.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/lsp/hints.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/lsp/protocol.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/lsp/remembered.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/lsp/stored.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/options.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/paths.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/reading.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/report.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/runs.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/schedule.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/cli/workers.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/empty.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/__init__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/core/__init__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/core/asked.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/core/fixes.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/core/inherited.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/core/signatures.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/__init__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/annotated.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/awaits.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/beyond.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/callers.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/consistent.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/decorated.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/fixtures.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/loose.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/managed.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/order.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/params.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/sides.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/strings.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/stubs/__init__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/stubs/chained.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/stubs/names.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/stubs/read.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/stubs/stubbed.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/taken.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/index/tuples.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/libraries/__init__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/libraries/generics.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/libraries/library.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/libraries/opened.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/libraries/operators.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/libraries/overloads.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/libraries/receivers.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/libraries/walks.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/aliases.json +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/awaited_overloads.json +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/bases.json +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/elements.json +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/functions.json +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/generic_attributes.json +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/overloads.json +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/platform.json +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/scalar_members.json +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/scalars.json +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/subscriptable.json +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/type_parameters.json +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/tables/variables.json +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/__init__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/aliased.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/bodies.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/callables.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/called.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/decided.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/displays.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/entered.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/fills.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/hinted.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/loops.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/narrowed.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/operated.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/ordered.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/shapes.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/stored.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/targets.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/fix/values/unchecked.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/jsonc.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/noqa.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/notebook.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/overrides.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/plugins/__init__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/plugins/flake8.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/plugins/options.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/plugins/pylint.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/py.typed +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/recording.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/__init__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/binding.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/calls.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/late.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/narrowing.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/parameters.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/rebinding.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/rebound.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/recorded.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/redundant.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/returning.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/scoped.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/signatures.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/rules/widened.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/source/__init__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/source/decorators.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/source/flow.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/source/keys.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/source/parsed.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/source/quoted.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/source/walked.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/constricter/trace.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/hatch_build.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/mypyc.txt +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/stdlib_tables/__init__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/stdlib_tables/__main__.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/stdlib_tables/accepting.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/stdlib_tables/configured.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/stdlib_tables/elements.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/stdlib_tables/forms.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/stdlib_tables/overloads.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/stdlib_tables/shed.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/stdlib_tables/stubs.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/stdlib_tables/templates.py +0 -0
- {python_constricter-0.4.4 → python_constricter-0.4.5}/stdlib_tables/written.py +0 -0
|
@@ -18,7 +18,7 @@ from constricter.cli.runs import CheckRun, FileRun, merged
|
|
|
18
18
|
from constricter.cli.workers import Workers, running, started
|
|
19
19
|
from constricter.empty import Empty
|
|
20
20
|
from constricter.fix.core.known import Callee, Hints, Outside
|
|
21
|
-
from constricter.fix.index import callers, decorated, loose, plain, project
|
|
21
|
+
from constricter.fix.index import callers, crossed, decorated, loose, plain, project
|
|
22
22
|
from constricter.fix.index.stubs import installed
|
|
23
23
|
from constricter.offences import UNANNOTATED_ATTRIBUTE, UNANNOTATED_PARAMETER, Edit, Fix
|
|
24
24
|
|
|
@@ -67,10 +67,15 @@ def settled(
|
|
|
67
67
|
again: list[int] = [
|
|
68
68
|
index
|
|
69
69
|
for index, run in enumerate(runs)
|
|
70
|
-
if fixing
|
|
70
|
+
if fixing
|
|
71
|
+
and paths[index] != STDIN
|
|
72
|
+
and (_unsettled(run, session, options.guesses) or _imports_read(modules, paths[index], run))
|
|
71
73
|
]
|
|
72
74
|
changed: list[int] = [
|
|
73
|
-
at
|
|
75
|
+
at
|
|
76
|
+
for at in range(len(runs))
|
|
77
|
+
if _reaching(paths[at], runs[at], onward=onward, signing=signing)
|
|
78
|
+
or (fixing and _imports_read(modules, paths[at], runs[at]))
|
|
74
79
|
]
|
|
75
80
|
for _ in range(HINT_ROUNDS - 1):
|
|
76
81
|
held: dict[int, FileRun] = {}
|
|
@@ -83,13 +88,37 @@ def settled(
|
|
|
83
88
|
latest: project.Index
|
|
84
89
|
fresh, latest = _round(paths, runs, (again, held), _How(check, options, session), modules)
|
|
85
90
|
modules = latest if onward or signing else modules
|
|
86
|
-
again = [
|
|
91
|
+
again = [
|
|
92
|
+
index
|
|
93
|
+
for index, run in fresh
|
|
94
|
+
if _unsettled(run, session, options.guesses) or _imports_read(modules, paths[index], run)
|
|
95
|
+
]
|
|
87
96
|
changed = [
|
|
88
|
-
index
|
|
97
|
+
index
|
|
98
|
+
for index, run in fresh
|
|
99
|
+
if _reaching(paths[index], run, onward=onward, signing=signing)
|
|
100
|
+
or _imports_read(modules, paths[index], run)
|
|
89
101
|
]
|
|
90
102
|
return runs
|
|
91
103
|
|
|
92
104
|
|
|
105
|
+
def _imports_read(modules: project.Index, path: Path, run: FileRun) -> bool:
|
|
106
|
+
"""Check whether a round of `--fix` imported a checked file's class whose members the file reads.
|
|
107
|
+
|
|
108
|
+
What's read of them had no type while the file didn't spell the class (`f: TextIO`, from the
|
|
109
|
+
standard-library tables, then `f.encoding`, on a corpus that checks `typing.py`).
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Whether it did.
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
return _changed(path, run) and any(
|
|
116
|
+
isinstance(made.offence.edit, Fix)
|
|
117
|
+
and crossed.reads_imported(modules, path, made.offence.edit.imports)
|
|
118
|
+
for made in cast("CheckRun", run).made
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
93
122
|
def _reaching(path: Path, run: FileRun, *, onward: bool, signing: bool) -> bool:
|
|
94
123
|
"""Check whether a round of `--fix` changed a file in a way the other files may see.
|
|
95
124
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
import bisect
|
|
5
|
+
import builtins
|
|
5
6
|
import re
|
|
6
7
|
import sys
|
|
7
8
|
from collections.abc import Callable, Iterator, Mapping, Sequence
|
|
@@ -11,7 +12,7 @@ from weakref import WeakKeyDictionary
|
|
|
11
12
|
|
|
12
13
|
from constricter.fix.core.known import Checking, Guarded, ImportPlan, Origin, Returns
|
|
13
14
|
from constricter.source.annotations import dotted, roots
|
|
14
|
-
from constricter.source.syntax import FunctionDef, Start, import_bindings
|
|
15
|
+
from constricter.source.syntax import FunctionDef, Start, generic_named, import_bindings
|
|
15
16
|
from constricter.source.walked import classes, of_type
|
|
16
17
|
|
|
17
18
|
_TYPE_CHECKING: Final = "TYPE_CHECKING"
|
|
@@ -22,6 +23,8 @@ _FUTURE: Final = "__future__"
|
|
|
22
23
|
_IMPORTORSKIP: Final = "pytest.importorskip" # a module it names may be missing
|
|
23
24
|
_MODNAME: Final = "modname" # its parameter
|
|
24
25
|
_DOT: Final = "."
|
|
26
|
+
BUILTINS: Final = frozenset(dir(builtins))
|
|
27
|
+
_NONE: Final[frozenset[str]] = frozenset()
|
|
25
28
|
_ANNOTATIONS: Final = "annotations" # `from __future__ import annotations` postpones them all
|
|
26
29
|
# Each module's names, for as long as its tree lives: the index reads them, then the check.
|
|
27
30
|
_Taken: TypeAlias = tuple[frozenset[str], frozenset[str]]
|
|
@@ -43,12 +46,14 @@ _BINDERS: Final = (
|
|
|
43
46
|
)
|
|
44
47
|
|
|
45
48
|
|
|
46
|
-
def plan(tree: ast.Module) -> ImportPlan:
|
|
49
|
+
def plan(tree: ast.Module, imported_values: frozenset[str] = _NONE) -> ImportPlan:
|
|
47
50
|
"""Read what `tree` already imports, every name it binds, and where its imports end.
|
|
48
51
|
|
|
49
52
|
Only imports that run count as binding a name to use (not those under `if TYPE_CHECKING:`: a
|
|
50
53
|
module's own annotations are evaluated when it's imported); every name bound anywhere in the
|
|
51
|
-
file is taken, so an added import never shadows one (nor a builtin).
|
|
54
|
+
file is taken, so an added import never shadows one (nor a builtin). `imported_values`: the
|
|
55
|
+
names it imports that another module binds as a value (`linked.values`), which with its own
|
|
56
|
+
functions and assignments say which builtins it shadows.
|
|
52
57
|
|
|
53
58
|
Returns:
|
|
54
59
|
A fresh plan for the module's fixes.
|
|
@@ -64,6 +69,7 @@ def plan(tree: ast.Module) -> ImportPlan:
|
|
|
64
69
|
taken,
|
|
65
70
|
_after(tree),
|
|
66
71
|
defined,
|
|
72
|
+
shadowed=_shadowed(tree) | (imported_values & BUILTINS),
|
|
67
73
|
checking=Checking(
|
|
68
74
|
_block(tree),
|
|
69
75
|
_lazy(tree, bound),
|
|
@@ -76,6 +82,7 @@ def plan(tree: ast.Module) -> ImportPlan:
|
|
|
76
82
|
),
|
|
77
83
|
postponed=_postponed(tree),
|
|
78
84
|
values=values,
|
|
85
|
+
generic=generic_named(tree),
|
|
79
86
|
)
|
|
80
87
|
|
|
81
88
|
|
|
@@ -149,6 +156,8 @@ def unwritable(annotation: str, planned: ImportPlan, nested: Mapping[str, str])
|
|
|
149
156
|
def added_origin(statement: str) -> Origin:
|
|
150
157
|
"""Read what an import `ImportPlan.spell` added binds: `from m import T`, or `import m`.
|
|
151
158
|
|
|
159
|
+
`import m.sub` binds `m`, as one in the source does (`syntax.import_bindings`).
|
|
160
|
+
|
|
152
161
|
Returns:
|
|
153
162
|
Its origin: `("m", "T")`, or `("m", None)`.
|
|
154
163
|
|
|
@@ -156,7 +165,7 @@ def added_origin(statement: str) -> Origin:
|
|
|
156
165
|
module: str
|
|
157
166
|
name: str
|
|
158
167
|
module, _, name = statement.removeprefix("from ").removeprefix("import ").partition(" import ")
|
|
159
|
-
return module, name
|
|
168
|
+
return (module, name) if name else (module.partition(_DOT)[0], None)
|
|
160
169
|
|
|
161
170
|
|
|
162
171
|
def added_dotted(statement: str) -> str:
|
|
@@ -264,6 +273,25 @@ def _bound(tree: ast.Module) -> dict[str, str]:
|
|
|
264
273
|
return {name: origin for name, origin, _ in import_bindings(_running(tree.body))}
|
|
265
274
|
|
|
266
275
|
|
|
276
|
+
def _shadowed(tree: ast.Module) -> frozenset[str]:
|
|
277
|
+
"""Name the builtins the module binds at its top level to a function or a value of its own.
|
|
278
|
+
|
|
279
|
+
`def str(val)` (`locale`'s), `str = str` (a Python 2 shim): a type written with the name would
|
|
280
|
+
name those, which a type checker refuses ("Variable not allowed in type expression"). A class
|
|
281
|
+
of the name (`enum`'s `property`) is a class, named as any is.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Them.
|
|
285
|
+
|
|
286
|
+
"""
|
|
287
|
+
found: set[str] = set()
|
|
288
|
+
stmt: ast.stmt
|
|
289
|
+
for stmt in _running(tree.body):
|
|
290
|
+
if isinstance(stmt, ast.FunctionDef | ast.AsyncFunctionDef | ast.Assign | ast.AnnAssign):
|
|
291
|
+
found.update(name for name in _names(stmt) if name in BUILTINS)
|
|
292
|
+
return frozenset(found)
|
|
293
|
+
|
|
294
|
+
|
|
267
295
|
def _defined(tree: ast.Module) -> dict[str, int]:
|
|
268
296
|
"""Map each name the module binds at its top level (or under a top-level `if`/`try`) to its line.
|
|
269
297
|
|
|
@@ -16,7 +16,7 @@ from constricter.fix.core.inherited import Beyond, Lineage
|
|
|
16
16
|
from constricter.fix.core.signatures import Expansion, ReadSignature
|
|
17
17
|
from constricter.offences import MAX_LENGTH, VAGUE
|
|
18
18
|
from constricter.overrides import override
|
|
19
|
-
from constricter.source.annotations import free_of, free_of_all, roots
|
|
19
|
+
from constricter.source.annotations import GENERIC_HOMES, free_of, free_of_all, roots
|
|
20
20
|
from constricter.source.declarations import GenericClass
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
@@ -40,6 +40,7 @@ Origin: TypeAlias = tuple[str, str | None]
|
|
|
40
40
|
SPELLED: Final = "="
|
|
41
41
|
# After a fixture's `request` parameter's name: the key its `request.param` is typed under (see `Typed`).
|
|
42
42
|
PARAM: Final = ".param"
|
|
43
|
+
OWN_SELF: Final = "self.own" # in a scope's types: the class whose method reads `self`, unannotated or not
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
# A `dict` itself, not a `UserDict`: every inference reads it, as fast as a `dict` is read.
|
|
@@ -164,7 +165,9 @@ class ImportPlan: # pylint: disable=too-many-instance-attributes
|
|
|
164
165
|
`bound`: each name its imports bind, and what that is (`io`, `io.BytesIO`); `taken`: every name
|
|
165
166
|
bound anywhere in it; `after`: the line added imports go after; `defined`: each name it binds
|
|
166
167
|
at its top level (an import, a class, a function, an assignment), and the line it's first bound
|
|
167
|
-
on; `
|
|
168
|
+
on; `shadowed`: the builtins it binds at its top level to what a checker reads as no class (a
|
|
169
|
+
function, an assignment, an import of a value), so no type is written with them; `added`: each
|
|
170
|
+
name an added import binds, and that import's statement, as `spell` chose them.
|
|
168
171
|
`guarded`: the names other checked files' types and a type checker's hints are written with
|
|
169
172
|
that the module imports (or is to import) under `if TYPE_CHECKING:` alone; `checking`: its own
|
|
170
173
|
such imports, where one goes, and whose types need one (see `Checking`); `postponed`: whether
|
|
@@ -175,30 +178,72 @@ class ImportPlan: # pylint: disable=too-many-instance-attributes
|
|
|
175
178
|
taken: frozenset[str]
|
|
176
179
|
after: int
|
|
177
180
|
defined: Mapping[str, int] = field(default_factory=dict[str, int])
|
|
181
|
+
shadowed: frozenset[str] = frozenset()
|
|
178
182
|
added: dict[str, str] = field(default_factory=dict[str, str])
|
|
179
183
|
guarded: Mapping[str, Guarded] = field(default_factory=dict[str, Guarded])
|
|
180
184
|
checking: Checking = field(default_factory=Checking)
|
|
181
185
|
postponed: bool = False
|
|
182
186
|
values: frozenset[str] = frozenset() # names it binds as values somewhere (see `imports.taken_names`)
|
|
187
|
+
generic: frozenset[str] = (
|
|
188
|
+
frozenset()
|
|
189
|
+
) # its classes of a generic's name, generic too (`syntax.generic_named`)
|
|
183
190
|
# Names it imports from another checked file that re-exports a type's factory there (`Outside`).
|
|
184
191
|
factories: frozenset[str] = frozenset()
|
|
185
192
|
|
|
193
|
+
def own_names(self) -> frozenset[str]:
|
|
194
|
+
"""Name what the module binds, or is to import, other than from where typing's generics come.
|
|
195
|
+
|
|
196
|
+
Its own `class Generator`, `from numpy.random import Generator`, `import numpy as np`: a
|
|
197
|
+
generic's name written bare (`Generator`) or through one (`np.random.Generator`) is then
|
|
198
|
+
that, not the generic missing its arguments (see `annotations.is_vague`).
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Them.
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
found: set[str] = {
|
|
205
|
+
name
|
|
206
|
+
for name in self.defined.keys() - self.generic
|
|
207
|
+
if name not in self.bound
|
|
208
|
+
or not {self.bound[name], self.bound[name].rpartition(_DOT)[0]} & GENERIC_HOMES
|
|
209
|
+
}
|
|
210
|
+
name: str
|
|
211
|
+
statement: str
|
|
212
|
+
for name, statement in self.added.items():
|
|
213
|
+
if statement.split()[1] not in GENERIC_HOMES:
|
|
214
|
+
found.add(name)
|
|
215
|
+
each: Guarded
|
|
216
|
+
for name, each in self.guarded.items():
|
|
217
|
+
if each.origin[0] not in GENERIC_HOMES:
|
|
218
|
+
found.add(name)
|
|
219
|
+
return frozenset(found)
|
|
220
|
+
|
|
186
221
|
def type_checking(self) -> str | None:
|
|
187
222
|
"""Name `typing.TYPE_CHECKING` for a new block, by an import that runs: the module reads it.
|
|
188
223
|
|
|
224
|
+
The block goes after `after`, so through an import the module has only where it binds the
|
|
225
|
+
name by then: an `import typing` below a `try:` of other imports isn't, and a new import
|
|
226
|
+
goes with the block.
|
|
227
|
+
|
|
189
228
|
Returns:
|
|
190
229
|
The name, or `None` if every way to write it is taken.
|
|
191
230
|
|
|
192
231
|
"""
|
|
193
|
-
|
|
232
|
+
qualified: str = "typing.TYPE_CHECKING"
|
|
233
|
+
found: str | None = self.named(qualified)
|
|
234
|
+
if found is not None and self.defined.get(found.partition(_DOT)[0], self.after + 1) <= self.after:
|
|
235
|
+
return found
|
|
236
|
+
return self._added(qualified, runs=True)
|
|
194
237
|
|
|
195
238
|
def spell(self, qualified: str, *, runs: bool = False) -> str | None:
|
|
196
239
|
"""Name `qualified` (`io.BufferedReader`) in this module, adding an import if it has to.
|
|
197
240
|
|
|
198
241
|
Through an import it has (see `named`), for type checking alone too (`checking`, or one
|
|
199
|
-
`guarded` already), else a new `from io import BufferedReader`, else a new `import io
|
|
200
|
-
|
|
201
|
-
|
|
242
|
+
`guarded` already), else a new `from io import BufferedReader`, else a new `import io`
|
|
243
|
+
(`import collections.abc`, binding `collections`, unless the module imports the name or
|
|
244
|
+
defines a class of it), but only binding a name nothing in the module binds. Under
|
|
245
|
+
`if TYPE_CHECKING:` for a module in `checking.lazy`, and not at all for one only some
|
|
246
|
+
platforms have. With `runs`, only through
|
|
202
247
|
an import that runs: the name is read as the module is imported (`TYPE_CHECKING` itself).
|
|
203
248
|
|
|
204
249
|
Returns:
|
|
@@ -208,6 +253,15 @@ class ImportPlan: # pylint: disable=too-many-instance-attributes
|
|
|
208
253
|
found: str | None
|
|
209
254
|
if (found := self.named(qualified) or (None if runs else self._checked(qualified))) is not None:
|
|
210
255
|
return found
|
|
256
|
+
return self._added(qualified, runs=runs)
|
|
257
|
+
|
|
258
|
+
def _added(self, qualified: str, *, runs: bool) -> str | None:
|
|
259
|
+
"""Name `qualified` by an import to add, as `spell` does past the module's own.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
The name, or `None` if every way to write it is taken.
|
|
263
|
+
|
|
264
|
+
"""
|
|
211
265
|
module: str
|
|
212
266
|
name: str
|
|
213
267
|
module, _, name = qualified.rpartition(".")
|
|
@@ -220,9 +274,14 @@ class ImportPlan: # pylint: disable=too-many-instance-attributes
|
|
|
220
274
|
if self._free(name, statement):
|
|
221
275
|
self.added[name] = statement
|
|
222
276
|
return name
|
|
223
|
-
statement = f"import {module}"
|
|
224
|
-
|
|
225
|
-
|
|
277
|
+
statement = f"import {module}" # `import collections.abc` binds `collections`
|
|
278
|
+
# What the module imports or defines a class of the name as, anywhere (`from distutils.core
|
|
279
|
+
# import Distribution`, `_collections_abc`'s own `Iterator`), is the one meant, or the same:
|
|
280
|
+
# no new import beside it, past a module of one name.
|
|
281
|
+
if _DOT in module and name in self.taken and name not in self.values:
|
|
282
|
+
return None
|
|
283
|
+
if self._free(top, statement):
|
|
284
|
+
self.added[top] = statement
|
|
226
285
|
return qualified
|
|
227
286
|
return None
|
|
228
287
|
|
|
@@ -310,7 +369,8 @@ class LibraryNames(NamedTuple):
|
|
|
310
369
|
every Python the module runs on parses an unpacked tuple in a subscript (`tuple[int, *Ts]`,
|
|
311
370
|
3.11's syntax), so an annotation may be written with one. `aliased`: whether `typing` has
|
|
312
371
|
`TypeAlias` on every Python the module runs on (3.10's; yes where that isn't known), so a new
|
|
313
|
-
import may name it.
|
|
372
|
+
import may name it. `written`: the installed and checked files' classes it names, none generic,
|
|
373
|
+
which a docstring's return may name (see `Lineages.written`).
|
|
314
374
|
"""
|
|
315
375
|
|
|
316
376
|
casts: frozenset[str] = frozenset()
|
|
@@ -323,6 +383,7 @@ class LibraryNames(NamedTuple):
|
|
|
323
383
|
aliases: Mapping[str, Expansion] = MappingProxyType({})
|
|
324
384
|
starred: bool = False
|
|
325
385
|
aliased: bool = True
|
|
386
|
+
written: frozenset[str] = frozenset()
|
|
326
387
|
|
|
327
388
|
|
|
328
389
|
class Returned(NamedTuple):
|
|
@@ -579,9 +640,12 @@ class Outside: # pylint: disable=too-many-instance-attributes
|
|
|
579
640
|
classes: Classes | None = None
|
|
580
641
|
hints: tuple[Hints, ...] = () # each checker's, in the order they were named
|
|
581
642
|
type_vars: frozenset[str] = frozenset()
|
|
643
|
+
defaulted: frozenset[str] = frozenset() # those of them with a default (see `project.defaulted`)
|
|
582
644
|
returned: Returns = field(default_factory=Returns)
|
|
583
645
|
guarded: Mapping[str, Guarded] = field(default_factory=dict[str, Guarded])
|
|
584
646
|
generics: frozenset[str] = frozenset() # other checked files' generic classes, as it spells them
|
|
647
|
+
# Those of theirs a type checker reads bare with their defaults (see `Module.defaulted_generics`).
|
|
648
|
+
defaulted_generics: frozenset[str] = frozenset()
|
|
585
649
|
# Those classes' type parameters and the members it takes of them (see `GenericClass`).
|
|
586
650
|
generic: Mapping[str, GenericClass] = field(default_factory=dict[str, GenericClass])
|
|
587
651
|
# Their descriptors, as it spells them, each with what an instance's attribute of it gives.
|
|
@@ -610,6 +674,8 @@ class Outside: # pylint: disable=too-many-instance-attributes
|
|
|
610
674
|
installed_aliases: Mapping[str, Expansion] = field(
|
|
611
675
|
default_factory=dict[str, Expansion],
|
|
612
676
|
)
|
|
677
|
+
# See `LibraryNames.written`
|
|
678
|
+
written: frozenset[str] = frozenset()
|
|
613
679
|
# The classes it imports under `if TYPE_CHECKING:` alone, which an annotation can name (see `offers.own`).
|
|
614
680
|
checking: Mapping[str, Guarded] = field(default_factory=dict[str, Guarded])
|
|
615
681
|
plain: frozenset[str] | None = None
|
|
@@ -4,9 +4,11 @@ Each file is checked knowing what the files it imports declare and return, the h
|
|
|
4
4
|
checker gave it, and what the index's other readers say of the names it imports (see `Outside`).
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import re
|
|
7
8
|
from collections.abc import Mapping
|
|
9
|
+
from dataclasses import replace
|
|
8
10
|
from pathlib import Path
|
|
9
|
-
from typing import TYPE_CHECKING
|
|
11
|
+
from typing import TYPE_CHECKING, Final
|
|
10
12
|
|
|
11
13
|
from constricter.fix.core.known import Guarded, Hints, Origin, Outside, Passed
|
|
12
14
|
from constricter.fix.index import (
|
|
@@ -38,6 +40,10 @@ if TYPE_CHECKING:
|
|
|
38
40
|
from constricter.fix.core.signatures import ReadSignature
|
|
39
41
|
|
|
40
42
|
|
|
43
|
+
# A dotted name in a type (`trio.SocketListener`): a class spelled through a module.
|
|
44
|
+
_DOTTED: Final = re.compile(r"\b[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)+")
|
|
45
|
+
|
|
46
|
+
|
|
41
47
|
def outside(modules: project.Index, path: Path, hinted: Mapping[Path, tuple[Hints, ...]]) -> Outside:
|
|
42
48
|
"""Find what's known of a file from outside it: what it imports from the others, and its hints.
|
|
43
49
|
|
|
@@ -60,17 +66,17 @@ def outside(modules: project.Index, path: Path, hinted: Mapping[Path, tuple[Hint
|
|
|
60
66
|
hints: tuple[Hints, ...] = offers.vetted(modules, path, hinted.get(path, ()))
|
|
61
67
|
# What only a hint can name: the classes the file imports for type checking alone.
|
|
62
68
|
own: offers.Own = offers.own(modules, path) if hints else offers.Own()
|
|
69
|
+
lineage: own_types.Lineages = own_types.lineages(modules, path, guarded, methods.lineage)
|
|
63
70
|
overloaded: dict[str, tuple[ReadSignature, ...]] = {
|
|
64
71
|
**stubbed.overloaded(modules, path, guarded),
|
|
65
72
|
**methods.signatures,
|
|
66
73
|
**own_methods.overloaded(modules, path, guarded),
|
|
67
74
|
}
|
|
68
|
-
|
|
75
|
+
found: Outside = Outside(
|
|
69
76
|
calls={**imported.calls, **side_calls, **decorated.own(modules, path)},
|
|
70
|
-
# With those of the classes the overloaded methods' returns import.
|
|
71
|
-
classes=crossed.guarded_classes(modules, path, imported, guarded),
|
|
72
77
|
hints=hints,
|
|
73
78
|
type_vars=project.type_vars(modules, path),
|
|
79
|
+
defaulted=project.defaulted(modules, path),
|
|
74
80
|
returned=awaits.returned(
|
|
75
81
|
modules,
|
|
76
82
|
path,
|
|
@@ -79,6 +85,7 @@ def outside(modules: project.Index, path: Path, hinted: Mapping[Path, tuple[Hint
|
|
|
79
85
|
),
|
|
80
86
|
guarded=guarded,
|
|
81
87
|
generics=imported.generics | own.generics,
|
|
88
|
+
defaulted_generics=imported.defaulted_generics,
|
|
82
89
|
generic=dict(imported.generic),
|
|
83
90
|
descriptors=dict(imported.descriptors),
|
|
84
91
|
callees=callers.callees(modules, path),
|
|
@@ -88,7 +95,8 @@ def outside(modules: project.Index, path: Path, hinted: Mapping[Path, tuple[Hint
|
|
|
88
95
|
installed_classes=stubbed.classes(modules, path),
|
|
89
96
|
read_members=crossed.member_names(modules, path),
|
|
90
97
|
installed_parameters=methods.parameters,
|
|
91
|
-
installed_lineage=
|
|
98
|
+
installed_lineage=lineage,
|
|
99
|
+
written=lineage.written(),
|
|
92
100
|
installed_aliases=methods.aliases,
|
|
93
101
|
checking=own.guarded,
|
|
94
102
|
plain=plain.classes(modules, path),
|
|
@@ -109,6 +117,26 @@ def outside(modules: project.Index, path: Path, hinted: Mapping[Path, tuple[Hint
|
|
|
109
117
|
emptied=beyond.emptied(modules, path),
|
|
110
118
|
factories=factories(modules, path),
|
|
111
119
|
)
|
|
120
|
+
# With those of the classes the types above import for type checking (an overloaded method's
|
|
121
|
+
# return, an awaited call's, an item's): read last, as each of them adds to `guarded`.
|
|
122
|
+
return replace(found, classes=crossed.guarded_classes(modules, path, imported, guarded, _spelled(found)))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _spelled(found: Outside) -> frozenset[str]:
|
|
126
|
+
"""Find the dotted names in the types found of a file from outside it: `trio.SocketListener`.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Them.
|
|
130
|
+
|
|
131
|
+
"""
|
|
132
|
+
types: list[str] = [
|
|
133
|
+
*found.calls.values(),
|
|
134
|
+
*found.awaits.values(),
|
|
135
|
+
*found.tuples.values(),
|
|
136
|
+
*found.unions.values(),
|
|
137
|
+
*found.returned.calls.values(),
|
|
138
|
+
]
|
|
139
|
+
return frozenset(match.group() for each in types for match in _DOTTED.finditer(each))
|
|
112
140
|
|
|
113
141
|
|
|
114
142
|
def factories(modules: project.Index, path: Path) -> frozenset[str]:
|
|
@@ -7,13 +7,23 @@ only where every name in a type means the same in the file as where it was writt
|
|
|
7
7
|
`spelling.portable`).
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
import ast
|
|
11
|
+
from collections.abc import Iterable, Iterator, Mapping
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from typing import Final, NamedTuple, TypeAlias
|
|
13
14
|
|
|
14
15
|
from constricter.fix.core.known import Classes, Guarded, Origin, Partial, Returns
|
|
15
16
|
from constricter.fix.index.modules import Index, Memo, Module, classes_under, exported, module_at
|
|
16
|
-
from constricter.fix.index.project import
|
|
17
|
+
from constricter.fix.index.project import (
|
|
18
|
+
BUILTINS,
|
|
19
|
+
CLASS,
|
|
20
|
+
HOPS,
|
|
21
|
+
NO_TYPE,
|
|
22
|
+
PARTIAL,
|
|
23
|
+
Imported,
|
|
24
|
+
definition,
|
|
25
|
+
starred_names,
|
|
26
|
+
)
|
|
17
27
|
from constricter.fix.index.spelling import (
|
|
18
28
|
calls,
|
|
19
29
|
generic_portable,
|
|
@@ -62,7 +72,15 @@ def imported(
|
|
|
62
72
|
checking: dict[str, Origin] = {
|
|
63
73
|
name: found.origin for name, found in guarded.items() if name not in target.names
|
|
64
74
|
}
|
|
75
|
+
# What it imports for type checking alone, `if TYPE_CHECKING:`: a parameter annotated with one is one.
|
|
76
|
+
own: dict[str, Origin] = {
|
|
77
|
+
name: origin
|
|
78
|
+
for name, origin in target.guarded.items()
|
|
79
|
+
if name not in target.names and name not in checking and origin[0] != target.name
|
|
80
|
+
}
|
|
65
81
|
_take(catalog, target, (checking, attributes), taken, guarded)
|
|
82
|
+
if own:
|
|
83
|
+
_take(catalog, target, (own, attributes), taken, guarded)
|
|
66
84
|
_take(catalog, target, (target.names, attributes), taken, guarded)
|
|
67
85
|
found: tuple[dict[str, str], Returns, dict[str, str]] = (
|
|
68
86
|
calls(catalog, path, guarded),
|
|
@@ -85,6 +103,7 @@ def imported(
|
|
|
85
103
|
Partial(found[2], {name: each for name, each in taken.partial.items() if each}),
|
|
86
104
|
taken.generic,
|
|
87
105
|
taken.descriptors,
|
|
106
|
+
frozenset(taken.defaulted_generics),
|
|
88
107
|
)
|
|
89
108
|
|
|
90
109
|
|
|
@@ -127,12 +146,21 @@ def _bound_members(module: Module, name: str, seen: set[str]) -> frozenset[str]:
|
|
|
127
146
|
)
|
|
128
147
|
|
|
129
148
|
|
|
130
|
-
def guarded_classes(
|
|
149
|
+
def guarded_classes(
|
|
150
|
+
catalog: Index,
|
|
151
|
+
path: Path,
|
|
152
|
+
found: Imported,
|
|
153
|
+
guarded: Mapping[str, Guarded],
|
|
154
|
+
spelled: frozenset[str],
|
|
155
|
+
) -> Classes:
|
|
131
156
|
"""Add the members of the classes `guarded` names that `found` hasn't taken, as the file names them.
|
|
132
157
|
|
|
133
158
|
Those of the imports later steps add for type checking (an overloaded method's return):
|
|
134
159
|
`df = pd.DataFrame(...).set_index("C")`, typed `DataFrame` by its import: `df.explode()` is one of
|
|
135
|
-
that class's methods in the same pass, not only once the import is written.
|
|
160
|
+
that class's methods in the same pass, not only once the import is written. And those `spelled`
|
|
161
|
+
names through a module the file imports, as the types those steps give write them
|
|
162
|
+
(`trio.SocketListener`, which `trio` re-exports): the file reads no such attribute until a fix
|
|
163
|
+
writes the type, so `found` took none of them.
|
|
136
164
|
|
|
137
165
|
Returns:
|
|
138
166
|
`found`'s classes, with theirs.
|
|
@@ -146,13 +174,24 @@ def guarded_classes(catalog: Index, path: Path, found: Imported, guarded: Mappin
|
|
|
146
174
|
for name, each in guarded.items()
|
|
147
175
|
if name not in found.guarded and name not in target.names and name not in found.classes.methods
|
|
148
176
|
}
|
|
149
|
-
|
|
177
|
+
through: dict[str, Origin] = {}
|
|
178
|
+
attributes: set[str] = set()
|
|
179
|
+
each: str
|
|
180
|
+
for each in spelled:
|
|
181
|
+
root: str = each.partition(".")[0]
|
|
182
|
+
if each not in found.classes.methods and root in target.names:
|
|
183
|
+
through[root] = target.names[root]
|
|
184
|
+
attributes.add(each.rpartition(".")[2])
|
|
185
|
+
if not fresh and not through:
|
|
150
186
|
return found.classes
|
|
151
187
|
taken: _Taken = _taking(
|
|
152
188
|
{key: dict(each) for key, each in found.classes.attributes.items()},
|
|
153
189
|
{key: dict(each) for key, each in found.classes.methods.items()},
|
|
154
190
|
)
|
|
155
|
-
|
|
191
|
+
if fresh:
|
|
192
|
+
_take(catalog, target, (fresh, None), taken, dict(guarded))
|
|
193
|
+
if through:
|
|
194
|
+
_take(catalog, target, (through, frozenset(attributes)), taken, dict(guarded))
|
|
156
195
|
return Classes(taken.attributes, taken.methods)
|
|
157
196
|
|
|
158
197
|
|
|
@@ -166,6 +205,7 @@ class _Taken(NamedTuple):
|
|
|
166
205
|
generics: set[str]
|
|
167
206
|
generic: dict[str, GenericClass]
|
|
168
207
|
descriptors: dict[str, str | None]
|
|
208
|
+
defaulted_generics: set[str]
|
|
169
209
|
|
|
170
210
|
|
|
171
211
|
def _taking(attributes: dict[str, dict[str, str]], methods: dict[str, dict[str, str]]) -> _Taken:
|
|
@@ -175,7 +215,7 @@ def _taking(attributes: dict[str, dict[str, str]], methods: dict[str, dict[str,
|
|
|
175
215
|
It, nothing else taken yet.
|
|
176
216
|
|
|
177
217
|
"""
|
|
178
|
-
return _Taken(attributes, methods, {}, {}, set(), {}, {})
|
|
218
|
+
return _Taken(attributes, methods, {}, {}, set(), {}, {}, set())
|
|
179
219
|
|
|
180
220
|
|
|
181
221
|
def _take(catalog: Index, target: Module, named: _Named, taken: _Taken, guarded: dict[str, Guarded]) -> None:
|
|
@@ -185,6 +225,8 @@ def _take(catalog: Index, target: Module, named: _Named, taken: _Taken, guarded:
|
|
|
185
225
|
for key, defined in spelled_classes(catalog, target, named, taken.generics):
|
|
186
226
|
if defined[1] in defined[0].generics:
|
|
187
227
|
taken.generics.add(key)
|
|
228
|
+
if defined[1] in defined[0].defaulted_generics:
|
|
229
|
+
taken.defaulted_generics.add(key)
|
|
188
230
|
if defined[1] in defined[0].generic_members:
|
|
189
231
|
klass: GenericClass = defined[0].generic_members[defined[1]]
|
|
190
232
|
taken.generic[key] = generic_portable(catalog.modules, (target, defined), key, klass, guarded)
|
|
@@ -210,6 +252,35 @@ def _take(catalog: Index, target: Module, named: _Named, taken: _Taken, guarded:
|
|
|
210
252
|
)
|
|
211
253
|
|
|
212
254
|
|
|
255
|
+
def reads_imported(catalog: Index, path: Path, statements: Iterable[str]) -> bool:
|
|
256
|
+
"""Check whether a file reads a member of a checked file's class that `statements` import into it.
|
|
257
|
+
|
|
258
|
+
It took none of the class's members while it didn't spell the class (see `_take`).
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Whether it does; not for a file `catalog` doesn't have.
|
|
262
|
+
|
|
263
|
+
"""
|
|
264
|
+
target: Module | None
|
|
265
|
+
if (target := module_at(catalog, path)) is None:
|
|
266
|
+
return False
|
|
267
|
+
statement: str
|
|
268
|
+
for statement in statements:
|
|
269
|
+
node: ast.stmt = ast.parse(statement).body[0]
|
|
270
|
+
names: list[ast.alias] = node.names if isinstance(node, ast.ImportFrom) else []
|
|
271
|
+
module: str = node.module or "" if isinstance(node, ast.ImportFrom) else ""
|
|
272
|
+
alias: ast.alias
|
|
273
|
+
for alias in names:
|
|
274
|
+
defined: tuple[Module, str] | None = definition(catalog.modules, (module, alias.name), CLASS)
|
|
275
|
+
if (
|
|
276
|
+
defined is not None
|
|
277
|
+
and not defined[0].installed
|
|
278
|
+
and not _declared_members(catalog, defined).isdisjoint(target.attributes)
|
|
279
|
+
):
|
|
280
|
+
return True
|
|
281
|
+
return False
|
|
282
|
+
|
|
283
|
+
|
|
213
284
|
def _written(spelled: str, attributes: frozenset[str]) -> bool:
|
|
214
285
|
"""Check whether a file can write a class as `spelled`: each part after its first an attribute it takes.
|
|
215
286
|
|
|
@@ -343,7 +414,9 @@ def _spelled_classes(
|
|
|
343
414
|
*classes_under(catalog, local, origin[0], CLASS),
|
|
344
415
|
*(
|
|
345
416
|
(f"{local}.{name}", where)
|
|
346
|
-
for name, where in (
|
|
417
|
+
for name, where in (
|
|
418
|
+
() if package is None else _reexported(catalog.modules, package).items()
|
|
419
|
+
)
|
|
347
420
|
if (named[1] is None or name in named[1])
|
|
348
421
|
and where[1] is not None
|
|
349
422
|
and where[0] != origin[0]
|
|
@@ -358,6 +431,16 @@ def _spelled_classes(
|
|
|
358
431
|
yield key, defined
|
|
359
432
|
|
|
360
433
|
|
|
434
|
+
def _reexported(modules: Mapping[str, Module], module: Module) -> Mapping[str, Origin]:
|
|
435
|
+
"""Name what a module imports from others, by name or everything (see `exported`, `starred_names`).
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
Each, by its name there.
|
|
439
|
+
|
|
440
|
+
"""
|
|
441
|
+
return {**starred_names(modules, module), **exported(module)} if module.stars else exported(module)
|
|
442
|
+
|
|
443
|
+
|
|
361
444
|
def _reexported_generics(modules: Mapping[str, Module], local: str, name: str) -> Iterator[str]:
|
|
362
445
|
"""Spell the generic classes module `name` re-exports (`from ._c import OrderedSet`) as `local.C`.
|
|
363
446
|
|
|
@@ -371,6 +454,6 @@ def _reexported_generics(modules: Mapping[str, Module], local: str, name: str) -
|
|
|
371
454
|
module: Module | None = modules.get(name)
|
|
372
455
|
reexported: str
|
|
373
456
|
origin: Origin
|
|
374
|
-
for reexported, origin in () if module is None else
|
|
457
|
+
for reexported, origin in () if module is None else _reexported(modules, module).items():
|
|
375
458
|
if origin[1] is not None and origin[0] != name and is_generic(modules, origin):
|
|
376
459
|
yield f"{local}.{reexported}"
|