# Wildcard (`*`) imports
See the [Python language reference for import statements].
## Basic functionality
### A simple `*` import
`exporter.py`:
```py
X: bool = True
```
`importer.py`:
```py
from exporter import *
reveal_type(X) # revealed: bool
print(Y) # error: [unresolved-reference]
```
### Overriding an existing definition
`exporter.py`:
```py
X: bool = True
```
`importer.py`:
```py
X = 42
reveal_type(X) # revealed: Literal[42]
from exporter import *
reveal_type(X) # revealed: bool
```
### Overridden by a later definition
`exporter.py`:
```py
X: bool = True
```
`importer.py`:
```py
from exporter import *
reveal_type(X) # revealed: bool
X = False
reveal_type(X) # revealed: Literal[False]
```
### Reaching across many modules
`a.py`:
```py
X: bool = True
```
`b.py`:
```py
from a import *
```
`c.py`:
```py
from b import *
```
`main.py`:
```py
from c import *
reveal_type(X) # revealed: bool
```
### A wildcard import constitutes a re-export
This is specified
[here](https://typing.python.org/en/latest/spec/distributing.html#import-conventions).
`a.pyi`:
```pyi
X: bool = True
```
`b.pyi`:
```pyi
Y: bool = False
```
`c.pyi`:
```pyi
from a import *
from b import Y
```
`main.py`:
```py
# `X` is accessible because the `*` import in `c` re-exports it from `c`
from c import X
# but `Y` is not because the `from b import Y` import does *not* constitute a re-export
from c import Y # error: [unresolved-import]
```
## Esoteric definitions and redefinintions
```toml
[environment]
python-version = "3.12"
```
We understand all public symbols defined in an external module as being imported by a `*` import,
not just those that are defined in `StmtAssign` nodes and `StmtAnnAssign` nodes. This section
provides tests for definitions, and redefinitions, that use more esoteric AST nodes.
### Global-scope symbols defined using walrus expressions
`exporter.py`:
```py
X = (Y := 3) + 4
```
`b.py`:
```py
from exporter import *
reveal_type(X) # revealed: Unknown | Literal[7]
reveal_type(Y) # revealed: Unknown | Literal[3]
```
### Global-scope symbols defined in many other ways
`exporter.py`:
```py
import typing
from collections import OrderedDict
from collections import OrderedDict as Foo
A, B = 1, (C := 2)
D: (E := 4) = (F := 5) # error: [invalid-type-form]
for G in [1]:
...
for (H := 4).whatever in [2]: # error: [unresolved-attribute]
...
class I: ...
def J(): ...
type K = int
class ContextManagerThatMightNotRunToCompletion:
def __enter__(self) -> "ContextManagerThatMightNotRunToCompletion":
return self
def __exit__(self, *args) -> typing.Literal[True]:
return True
with ContextManagerThatMightNotRunToCompletion() as L:
U = ...
match 42:
case {"something": M}:
...
case [*N]:
...
case [O]:
...
case P | Q: # error: [invalid-syntax] "name capture `P` makes remaining patterns unreachable"
...
case object(foo=R):
...
match 56:
case x if something_unresolvable: # error: [unresolved-reference]
...
case object(S):
...
match 12345:
case x if something_unresolvable: # error: [unresolved-reference]
...
case T:
...
def boolean_condition() -> bool:
return True
if boolean_condition():
V = ...
while boolean_condition():
W = ...
```
`importer.py`:
```py
from exporter import *
# fmt: off
print((
A,
B,
C,
D,
E,
F,
G, # error: [possibly-unresolved-reference]
H, # error: [possibly-unresolved-reference]
I,
J,
K,
L,
M, # error: [possibly-unresolved-reference]
N, # error: [possibly-unresolved-reference]
O, # error: [possibly-unresolved-reference]
P, # error: [possibly-unresolved-reference]
Q, # error: [possibly-unresolved-reference]
R, # error: [possibly-unresolved-reference]
S, # error: [possibly-unresolved-reference]
T, # error: [possibly-unresolved-reference]
U, # TODO: could emit [possibly-unresolved-reference here] (https://github.com/astral-sh/ruff/issues/16996)
V, # error: [possibly-unresolved-reference]
W, # error: [possibly-unresolved-reference]
typing,
OrderedDict,
Foo,
))
```
### Esoteric possible redefinitions following definitely bound prior definitions
There should be no complaint about the symbols being possibly unbound in `b.py` here: although the
second definition might or might not take place, each symbol is definitely bound by a prior
definition.
`exporter.py`:
```py
from typing import Literal
A = 1
B = 2
C = 3
D = 4
E = 5
F = 6
G = 7
H = 8
I = 9
J = 10
K = 11
L = 12
for A in [1]:
...
match 42:
case {"something": B}:
...
case [*C]:
...
case [D]:
...
case E | F: # error: [invalid-syntax] "name capture `E` makes remaining patterns unreachable"
...
case object(foo=G):
...
case object(H):
...
case I:
...
def boolean_condition() -> bool:
return True
if boolean_condition():
J = ...
while boolean_condition():
K = ...
class ContextManagerThatMightNotRunToCompletion:
def __enter__(self) -> "ContextManagerThatMightNotRunToCompletion":
return self
def __exit__(self, *args) -> Literal[True]:
return True
with ContextManagerThatMightNotRunToCompletion():
L = ...
```
`importer.py`:
```py
from exporter import *
print(A)
print(B)
print(C)
print(D)
print(E)
print(F)
print(G)
print(H)
print(I)
print(J)
print(K)
print(L)
```
### Esoteric possible definitions prior to definitely bound prior redefinitions
The same principle applies here to the symbols in `b.py`. Although the first definition might or
might not take place, each symbol is definitely bound by a later definition.
`exporter.py`:
```py
from typing import Literal
for A in [1]:
...
match 42:
case {"something": B}:
...
case [*C]:
...
case [D]:
...
case E | F: # error: [invalid-syntax] "name capture `E` makes remaining patterns unreachable"
...
case object(foo=G):
...
case object(H):
...
case I:
...
def boolean_condition() -> bool:
return True
if boolean_condition():
J = ...
while boolean_condition():
K = ...
class ContextManagerThatMightNotRunToCompletion:
def __enter__(self) -> "ContextManagerThatMightNotRunToCompletion":
return self
def __exit__(self, *args) -> Literal[True]:
return True
with ContextManagerThatMightNotRunToCompletion():
L = ...
A = 1
B = 2
C = 3
D = 4
E = 5
F = 6
G = 7
H = 8
I = 9
J = 10
K = 11
L = 12
```
`importer.py`:
```py
from exporter import *
print(A)
print(B)
print(C)
print(D)
print(E)
print(F)
print(G)
print(H)
print(I)
print(J)
print(K)
print(L)
```
### Definitions in function-like scopes are not global definitions
Except for some cases involving walrus expressions inside comprehension scopes.
`exporter.py`:
```py
class Iterator:
def __next__(self) -> int:
return 42
class Iterable:
def __iter__(self) -> Iterator:
return Iterator()
[a for a in Iterable()]
{b for b in Iterable()}
{c: c for c in Iterable()}
(d for d in Iterable())
lambda e: (f := 42)
# Definitions created by walruses in a comprehension scope are unique;
# they "leak out" of the scope and are stored in the surrounding scope
[(g := h * 2) for h in Iterable()]
[i for j in Iterable() if (i := j - 10) > 0]
{(k := l * 2): (m := l * 3) for l in Iterable()}
list(((o := p * 2) for p in Iterable()))
# A walrus expression nested inside several scopes *still* leaks out
# to the global scope:
[[[[(q := r) for r in Iterable()]] for _ in range(42)] for _ in range(42)]
# A walrus inside a lambda inside a comprehension does not leak out
[(lambda s=s: (t := 42))() for s in Iterable()]
```
`importer.py`:
```py
from exporter import *
# error: [unresolved-reference]
reveal_type(a) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(b) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(c) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(d) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(e) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(f) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(h) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(j) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(p) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(r) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(s) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(t) # revealed: Unknown
# TODO: these should all reveal `Unknown | int` and should not emit errors.
# (We don't generally model elsewhere in ty that bindings from walruses
# "leak" from comprehension scopes into outer scopes, but we should.)
# See https://github.com/astral-sh/ruff/issues/16954
# error: [unresolved-reference]
reveal_type(g) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(i) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(k) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(m) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(o) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(q) # revealed: Unknown
```
### An annotation without a value is a definition in a stub but not a `.py` file
`a.pyi`:
```pyi
X: bool
```
`b.py`:
```py
Y: bool
```
`c.py`:
```py
from a import *
from b import *
reveal_type(X) # revealed: bool
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
```
## Which symbols are exported
Not all symbols in the global namespace are considered "public". As a result, not all symbols bound
in the global namespace of an `exporter.py` module will be imported by a `from exporter import *`
statement in an `importer.py` module. The tests in this section elaborate on these semantics.
### Global-scope names starting with underscores
Global-scope names starting with underscores are not imported from a `*` import (unless the
exporting module has an `__all__` symbol in its global scope, and the underscore-prefixed symbols
are included in `__all__`):
`exporter.py`:
```py
_private: bool = False
__protected: bool = False
__dunder__: bool = False
___thunder___: bool = False
Y: bool = True
```
`importer.py`:
```py
from exporter import *
# error: [unresolved-reference]
reveal_type(_private) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(__protected) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(__dunder__) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(___thunder___) # revealed: Unknown
reveal_type(Y) # revealed: bool
```
### All public symbols are considered re-exported from `.py` files
For `.py` files, we should consider all public symbols in the global namespace exported by that
module when considering which symbols are made available by a `*` import. Here, `b.py` does not use
the explicit `from a import X as X` syntax to explicitly mark it as publicly re-exported, and `X` is
not included in `__all__`; whether it should be considered a "public name" in module `b` is
ambiguous.
We should consider `X` bound in `c.py`. However, we could consider adding an opt-in rule to warn the
user when they use `X` in `c.py` that it was neither included in `b.__all__` nor marked as an
explicit re-export from `b` through the "redundant alias" convention.
`a.py`:
```py
X: bool = True
```
`b.py`:
```py
from a import X
```
`c.py`:
```py
from b import *
# TODO: we could consider an opt-in diagnostic (see prose commentary above)
reveal_type(X) # revealed: bool
```
### Only explicit re-exports are considered re-exported from `.pyi` files
For `.pyi` files, we should consider all imports "private to the stub" unless they are included in
`__all__` or use the explicit `from foo import X as X` syntax.
`a.pyi`:
```pyi
X: bool = True
Y: bool = True
```
`b.pyi`:
```pyi
from a import X, Y as Y
```
`c.py`:
```py
from b import *
# This error is correct, as `X` is not considered re-exported from module `b`:
#
# error: [unresolved-reference]
reveal_type(X) # revealed: Unknown
reveal_type(Y) # revealed: bool
```
### An implicit import in a `.pyi` file later overridden by another assignment
`a.pyi`:
```pyi
X: bool = True
```
`b.pyi`:
```pyi
from a import X
X: bool = False
```
`c.py`:
```py
from b import *
reveal_type(X) # revealed: bool
```
## Visibility constraints
If an `importer` module contains a `from exporter import *` statement in its global namespace, the
statement will *not* necessarily import *all* symbols that have definitions in `exporter.py`'s
global scope. For any given symbol in `exporter.py`'s global scope, that symbol will *only* be
imported by the `*` import if at least one definition for that symbol is visible from the *end* of
`exporter.py`'s global scope.
For example, say that `exporter.py` contains a symbol `X` in its global scope, and the definition
for `X` in `exporter.py` has visibility constraints vis1
. The
`from exporter import *` statement in `importer.py` creates a definition for `X` in `importer`, and
there are visibility constraints vis2
on the import statement in
`importer.py`. This means that the overall visibility constraints on the `X` definnition created by
the import statement in `importer.py` will be vis1 AND vis2
.
A visibility constraint in the external module must be understood and evaluated whether or not its
truthiness can be statically determined.
### Statically known branches in the external module
```toml
[environment]
python-version = "3.11"
```
`exporter.py`:
```py
import sys
if sys.version_info >= (3, 11):
X: bool = True
else:
Y: bool = False
Z: int = 42
```
`importer.py`:
```py
import sys
Z: bool = True
from exporter import *
reveal_type(X) # revealed: bool
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
# The `*` import is not considered a redefinition
# of the global variable `Z` in this module, as the symbol in
# the `a` module is in a branch that is statically known
# to be dead code given the `python-version` configuration.
# Thus this still reveals `Literal[True]`.
reveal_type(Z) # revealed: Literal[True]
```
### Multiple `*` imports with always-false visibility constraints
Our understanding of visibility constraints in an external module remains accurate, even if there
are multiple `*` imports from that module.
```toml
[environment]
python-version = "3.11"
```
`exporter.py`:
```py
import sys
if sys.version_info >= (3, 12):
Z: str = "foo"
```
`importer.py`:
```py
Z = True
from exporter import *
from exporter import *
from exporter import *
reveal_type(Z) # revealed: Literal[True]
```
### Ambiguous visibility constraints
Some constraints in the external module may resolve to an "ambiguous truthiness". For these, we
should emit `possibly-unresolved-reference` diagnostics when they are used in the module in which
the `*` import occurs.
`exporter.py`:
```py
def coinflip() -> bool:
return True
if coinflip():
A = 1
B = 2
else:
B = 3
```
`importer.py`:
```py
from exporter import *
# error: [possibly-unresolved-reference]
reveal_type(A) # revealed: Unknown | Literal[1]
reveal_type(B) # revealed: Unknown | Literal[2, 3]
```
### Visibility constraints in the importing module
`exporter.py`:
```py
A = 1
```
`importer.py`:
```py
def coinflip() -> bool:
return True
if coinflip():
from exporter import *
# error: [possibly-unresolved-reference]
reveal_type(A) # revealed: Unknown | Literal[1]
```
### Visibility constraints in the exporting module *and* the importing module
```toml
[environment]
python-version = "3.11"
```
`exporter.py`:
```py
import sys
if sys.version_info >= (3, 12):
A: bool = True
def coinflip() -> bool:
return True
if coinflip():
B: bool = True
```
`importer.py`:
```py
import sys
if sys.version_info >= (3, 12):
from exporter import *
# it's correct to have no diagnostics here as this branch is unreachable
reveal_type(A) # revealed: Unknown
reveal_type(B) # revealed: bool
else:
from exporter import *
# error: [unresolved-reference]
reveal_type(A) # revealed: Unknown
# error: [possibly-unresolved-reference]
reveal_type(B) # revealed: bool
# error: [unresolved-reference]
reveal_type(A) # revealed: Unknown
# error: [possibly-unresolved-reference]
reveal_type(B) # revealed: bool
```
## Relative `*` imports
Relative `*` imports are also supported by Python:
`a/__init__.py`:
```py
```
`a/foo.py`:
```py
X: bool = True
```
`a/bar.py`:
```py
from .foo import *
reveal_type(X) # revealed: bool
```
## Star imports with `__all__`
If a module `x` contains `__all__`, all symbols included in `x.__all__` are imported by
`from x import *` (but no other symbols are).
### Simple tuple `__all__`
`exporter.py`:
```py
__all__ = ("X", "_private", "__protected", "__dunder__", "___thunder___")
X: bool = True
_private: bool = True
__protected: bool = True
__dunder__: bool = True
___thunder___: bool = True
Y: bool = False
```
`importer.py`:
```py
from exporter import *
reveal_type(X) # revealed: bool
reveal_type(_private) # revealed: bool
reveal_type(__protected) # revealed: bool
reveal_type(__dunder__) # revealed: bool
reveal_type(___thunder___) # revealed: bool
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
```
### Simple list `__all__`
`exporter.py`:
```py
__all__ = ["X"]
X: bool = True
Y: bool = False
```
`importer.py`:
```py
from exporter import *
reveal_type(X) # revealed: bool
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
```
### `__all__` with additions later on in the global scope
The
[typing spec](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)
lists certain modifications to `__all__` that must be understood by type checkers.
`a.py`:
```py
FOO: bool = True
__all__ = ["FOO"]
```
`b.py`:
```py
import a
from a import *
__all__ = ["A"]
__all__ += ["B"]
__all__.append("C")
__all__.extend(["D"])
__all__.extend(a.__all__)
A: bool = True
B: bool = True
C: bool = True
D: bool = True
E: bool = False
```
`c.py`:
```py
from b import *
reveal_type(A) # revealed: bool
reveal_type(B) # revealed: bool
reveal_type(C) # revealed: bool
reveal_type(D) # revealed: bool
reveal_type(FOO) # revealed: bool
# error: [unresolved-reference]
reveal_type(E) # revealed: Unknown
```
### `__all__` with subtractions later on in the global scope
Whereas there are many ways of adding to `__all__` that type checkers must support, there is only
one way of subtracting from `__all__` that type checkers are required to support:
`exporter.py`:
```py
__all__ = ["A", "B"]
__all__.remove("B")
A: bool = True
B: bool = True
```
`importer.py`:
```py
from exporter import *
reveal_type(A) # revealed: bool
# error: [unresolved-reference]
reveal_type(B) # revealed: Unknown
```
### Invalid `__all__`
If `a.__all__` contains a member that does not refer to a symbol with bindings in the global scope,
a wildcard import from module `a` will fail at runtime.
TODO: Should we:
1. Emit a diagnostic at the invalid definition of `__all__` (which will not fail at runtime)?
1. Emit a diagnostic at the star-import from the module with the invalid `__all__` (which *will*
fail at runtime)?
1. Emit a diagnostic on both?
`exporter.py`:
```py
__all__ = ["a", "b"]
a = 42
```
`importer.py`:
```py
# TODO we should consider emitting a diagnostic here (see prose description above)
from exporter import * # fails with `AttributeError: module 'foo' has no attribute 'b'` at runtime
```
### Dynamic `__all__`
If `__all__` contains members that are dynamically computed, we should check that all members of
`__all__` are assignable to `str`. For the purposes of evaluating `*` imports, however, we should
treat the module as though it has no `__all__` at all: all global-scope members of the module should
be considered imported by the import statement. We should probably also emit a warning telling the
user that we cannot statically determine the elements of `__all__`.
`exporter.py`:
```py
def f() -> str:
return "f"
def g() -> int:
return 42
# TODO we should emit a warning here for the dynamically constructed `__all__` member.
__all__ = [f()]
```
`importer.py`:
```py
from exporter import *
# At runtime, `f` is imported but `g` is not; to avoid false positives, however,
# we treat `a` as though it does not have `__all__` at all,
# which would imply that both symbols would be present.
reveal_type(f) # revealed: def f() -> str
reveal_type(g) # revealed: def g() -> int
```
### `__all__` conditionally defined in a statically known branch
```toml
[environment]
python-version = "3.11"
```
`exporter.py`:
```py
import sys
X: bool = True
if sys.version_info >= (3, 11):
__all__ = ["X", "Y"]
Y: bool = True
else:
__all__ = ("Z",)
Z: bool = True
```
`importer.py`:
```py
from exporter import *
reveal_type(X) # revealed: bool
reveal_type(Y) # revealed: bool
# error: [unresolved-reference]
reveal_type(Z) # revealed: Unknown
```
### `__all__` conditionally defined in a statically known branch (2)
The same example again, but with a different `python-version` set:
```toml
[environment]
python-version = "3.10"
```
`exporter.py`:
```py
import sys
X: bool = True
if sys.version_info >= (3, 11):
__all__ = ["X", "Y"]
Y: bool = True
else:
__all__ = ("Z",)
Z: bool = True
```
`importer.py`:
```py
from exporter import *
# error: [unresolved-reference]
reveal_type(X) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
reveal_type(Z) # revealed: bool
```
### `__all__` conditionally mutated in a statically known branch
```toml
[environment]
python-version = "3.11"
```
`exporter.py`:
```py
import sys
__all__ = []
X: bool = True
if sys.version_info >= (3, 11):
__all__.extend(["X", "Y"])
Y: bool = True
else:
__all__.append("Z")
Z: bool = True
```
`importer.py`:
```py
from exporter import *
reveal_type(X) # revealed: bool
reveal_type(Y) # revealed: bool
# error: [unresolved-reference]
reveal_type(Z) # revealed: Unknown
```
### `__all__` conditionally mutated in a statically known branch (2)
The same example again, but with a different `python-version` set:
```toml
[environment]
python-version = "3.10"
```
`exporter.py`:
```py
import sys
__all__ = []
X: bool = True
if sys.version_info >= (3, 11):
__all__.extend(["X", "Y"])
Y: bool = True
else:
__all__.append("Z")
Z: bool = True
```
`importer.py`:
```py
from exporter import *
# error: [unresolved-reference]
reveal_type(X) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
reveal_type(Z) # revealed: bool
```
### Empty `__all__`
An empty `__all__` is valid, but a `*` import from a module with an empty `__all__` results in 0
bindings being added from the import:
`a.py`:
```py
X: bool = True
__all__ = ()
```
`b.py`:
```py
Y: bool = True
__all__ = []
```
`c.py`:
```py
from a import *
from b import *
# error: [unresolved-reference]
reveal_type(X) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
```
### `__all__` in a stub file
If a name is included in `__all__` in a stub file, it is considered re-exported even if it was only
defined using an import without the explicit `from foo import X as X` syntax:
`a.pyi`:
```pyi
X: bool = True
Y: bool = True
```
`b.pyi`:
```pyi
from a import X, Y
__all__ = ["X", "Z"]
Z: bool = True
Nope: bool = True
```
`c.py`:
```py
from b import *
# `X` is re-exported from `b.pyi` due to presence in `__all__`
reveal_type(X) # revealed: bool
# This diagnostic is accurate: `Y` does not use the "redundant alias" convention in `b.pyi`,
# nor is it included in `b.__all__`, so it is not exported from `b.pyi`. It would still be
# an error if it used the "redundant alias" convention as `__all__` would take precedence.
#
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
# `Z` is defined in `b.pyi` and included in `__all__`
reveal_type(Z) # revealed: bool
# error: [unresolved-reference]
reveal_type(Nope) # revealed: Unknown
```
## `global` statements in non-global scopes
A `global` statement in a nested function scope, combined with a definition in the same function
scope of the name that was declared `global`, can add a symbol to the global namespace.
`a.py`:
```py
def f():
global g, h
g: bool = True
f()
```
`b.py`:
```py
from a import *
reveal_type(f) # revealed: def f() -> Unknown
# TODO: we're undecided about whether we should consider this a false positive or not.
# Mutating the global scope to add a symbol from an inner scope will not *necessarily* result
# in the symbol being bound from the perspective of other modules (the function that creates
# the inner scope, and adds the symbol to the global scope, might never be called!)
# See discussion in https://github.com/astral-sh/ruff/pull/16959
#
# error: [unresolved-reference]
reveal_type(g) # revealed: Unknown
# this diagnostic is accurate, though!
# error: [unresolved-reference]
reveal_type(h) # revealed: Unknown
```
## Cyclic star imports
Believe it or not, this code does *not* raise an exception at runtime!
`a.py`:
```py
from b import *
A: bool = True
```
`b.py`:
```py
from a import *
B: bool = True
```
`c.py`:
```py
from a import *
reveal_type(A) # revealed: bool
reveal_type(B) # revealed: bool
```
## Integration test: `collections.abc`
The `collections.abc` standard-library module provides a good integration test, as all its symbols
are present due to `*` imports.
```py
import collections.abc
reveal_type(collections.abc.Sequence) # revealed:
reveal_type(collections.abc.Callable) # revealed: typing.Callable
reveal_type(collections.abc.Set) # revealed:
```
## Invalid `*` imports
### Unresolved module
If the module is unresolved, we emit a diagnostic just like for any other unresolved import:
```py
# TODO: not a great error message
from foo import * # error: [unresolved-import] "Cannot resolve imported module `foo`"
```
### Nested scope
A `*` import in a nested scope are always a syntax error. Ty does not infer any bindings from them:
`exporter.py`:
```py
X: bool = True
```
`importer.py`:
```py
def f():
# TODO: we should emit a syntax error here (tracked by https://github.com/astral-sh/ruff/issues/17412)
from exporter import *
# error: [unresolved-reference]
reveal_type(X) # revealed: Unknown
```
### `*` combined with other aliases in the list
`a.py`:
```py
X: bool = True
_Y: bool = False
_Z: bool = True
```
`b.py`:
```py
from a import *, _Y # error: [invalid-syntax]
# The import statement above is invalid syntax,
# but it's pretty obvious that the user wanted to do a `*` import,
# so we import all public names from `a` anyway, to minimize cascading errors
reveal_type(X) # revealed: bool
reveal_type(_Y) # revealed: bool
```
These tests are more to assert that we don't panic on these various kinds of invalid syntax than
anything else:
`c.py`:
```py
from a import *, _Y # error: [invalid-syntax]
from a import _Y, *, _Z # error: [invalid-syntax]
from a import *, _Y as fooo # error: [invalid-syntax]
from a import *, *, _Y # error: [invalid-syntax]
```
[python language reference for import statements]: https://docs.python.org/3/reference/simple_stmts.html#the-import-statement