[ty] Make implicit submodule imports only occur in global scope (#21370)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}) (push) Blocked by required conditions
CI / cargo test (macos-latest) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / ty completion evaluation (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks instrumented (ruff) (push) Blocked by required conditions
CI / benchmarks instrumented (ty) (push) Blocked by required conditions
CI / benchmarks walltime (medium|multithreaded) (push) Blocked by required conditions
CI / benchmarks walltime (small|large) (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

This loses any ability to have "per-function" implicit submodule
imports, to avoid the "ok but now we need per-scope imports" and "ok but
this should actually introduce a global that only exists during this
function" problems. A simple and clean implementation with no weird
corners.

Fixes https://github.com/astral-sh/ty/issues/1482
This commit is contained in:
Aria Desires 2025-11-10 18:59:48 -05:00 committed by GitHub
parent 2bc6c78e26
commit 9ce3230add
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 16 additions and 29 deletions

View file

@ -9,18 +9,16 @@ This file currently covers the following details:
- **froms are locals**: a `from..import` can only define locals, it does not have global
side-effects. Specifically any submodule attribute `a` that's implicitly introduced by either
`from .a import b` or `from . import a as b` (in an `__init__.py(i)`) is a local and not a
global. 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).
global. However we only introduce this symbol if the `from..import` is in global-scope. This
means imports at the start of a file work as you'd expect, while imports in a function don't
introduce submodule attributes.
- **first from first serve**: only the *first* `from..import` in an `__init__.py(i)` that imports a
particular 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).
introduces it as an attribute of the package. By "first" we mean "the first time in global
scope".
- **dot re-exports**: `from . import a` in an `__init__.pyi` is considered a re-export of `a`
(equivalent to `from . import a as a`). This is required to properly handle many stubs in the
@ -949,9 +947,8 @@ def funcmod(x: int) -> int:
## LHS `from` Imports In Functions
If a `from` import occurs in a function, LHS symbols should only be visible in that function. This
very blatantly is not runtime-accurate, but exists to try to force you to write "obviously
deterministically correct" imports instead of relying on execution order.
If a `from` import occurs in a function, we simply ignore its LHS effects to avoid modeling
execution-order-specific behaviour (and to discourage people writing code that has it).
`mypackage/__init__.py`:
@ -959,13 +956,14 @@ deterministically correct" imports instead of relying on execution order.
def run1():
from .funcmod import other
# TODO: this would be nice to support
# error: [unresolved-reference]
funcmod.funcmod(1)
def run2():
from .funcmod import other
# TODO: this is just a bug! We only register the first
# import of `funcmod` in the entire file, and not per-scope!
# TODO: this would be nice to support
# error: [unresolved-reference]
funcmod.funcmod(2)