-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] introduce local variables for from imports of submodules in __init__.py(i)
#21173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Diagnostic diff on typing conformance testsChanges were detected when running ty on typing conformance tests--- old-output.txt 2025-11-05 14:53:56.780666414 +0000
+++ new-output.txt 2025-11-05 14:54:00.083684704 +0000
@@ -1,4 +1,4 @@
-fatal[panic] Panicked at /home/runner/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/664750a/src/function/execute.rs:464:17 when checking `/home/runner/work/ruff/ruff/typing/conformance/tests/aliases_typealiastype.py`: `infer_definition_types(Id(18af5)): execute: too many cycle iterations`
+fatal[panic] Panicked at /home/runner/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/664750a/src/function/execute.rs:464:17 when checking `/home/runner/work/ruff/ruff/typing/conformance/tests/aliases_typealiastype.py`: `infer_definition_types(Id(18b08)): execute: too many cycle iterations`
_directives_deprecated_library.py:15:31: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
_directives_deprecated_library.py:30:26: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `str`
_directives_deprecated_library.py:36:41: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `Self@__add__` |
|
|
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
unresolved-attribute |
6 | 612 | 10 |
invalid-assignment |
7 | 0 | 0 |
invalid-argument-type |
2 | 1 | 0 |
unused-ignore-comment |
3 | 0 | 0 |
unresolved-import |
0 | 2 | 0 |
call-non-callable |
1 | 0 | 0 |
possibly-missing-import |
1 | 0 | 0 |
possibly-unresolved-reference |
0 | 1 | 0 |
| Total | 20 | 616 | 10 |
CodSpeed Performance ReportMerging #21173 will not alter performanceComparing Summary
|
|
I believe the TL;DR of this PR is now:
As a reminder, "re-exported" is only an applicable concern for definitions in .pyi files, so both kinds of definitions will be fully visible in .py files. |
from..import effectsfrom imports of submodules in __init__.py(i)
available_submodule_attributes should probably be considered last instead of first
astral-sh/ty#1488
|
See astral-sh/ty#133 (comment) for a list of followups that I am intentionally punting on in this PR (or the sea of references right above this comment...) |



This rips out the previous implementation in favour of a new implementation with 3 rules:
froms are locals: a
from..importcan only define locals, it does not have globalside-effects. Specifically any submodule attribute
athat's implicitly introduced by eitherfrom .a import borfrom . import a as b(in an__init__.py(i)) is a local and not aglobal. If you do such an import at the top of a file you won't notice this. However if you do
such an import in a function, that means it will only be function-scoped (so you'll need to do
it in every function that wants to access it, making your code less sensitive to execution
order).
first from first serve: only the first
from..importin an__init__.py(i)that imports aparticular direct submodule of the current package introduces that submodule as a local.
Subsequent imports of the submodule will not introduce that local. This reflects the fact that
in actual python only the first import of a submodule (in the entire execution of the program)
introduces it as an attribute of the package. By "first" we mean "the first time in this scope
(or any parent scope)". This pairs well with the fact that we are specifically introducing a
local (as long as you don't accidentally shadow or overwrite the local).
dot re-exports:
from . import ain an__init__.pyiis considered a re-export ofa(equivalent to
from . import a as a). This is required to properly handle many stubs in thewild. Currently it must be exactly
from . import ....This implementation is intentionally limited/conservative (notably, often requiring a from import to be relative). I'm going to file a ton of followups for improvements so that their impact can be evaluated separately.
Fixes astral-sh/ty#133