ruff/crates/ty_python_semantic/resources/mdtest/import/basic.md
Aria Desires 6a1e91ce97
[ty] Check typeshed VERSIONS for parent modules when reporting failed stdlib imports (#20908)
This is a drive-by improvement that I stumbled backwards into while
looking into

* https://github.com/astral-sh/ty/issues/296

I was writing some simple tests for "thing not in old version of stdlib"
diagnostics and checked what was added in 3.14, and saw
`compression.zstd` and to my surprise discovered that `import
compression.zstd` and `from compression import zstd` had completely
different quality diagnostics.

This is because `compression` and `compression.zstd` were *both*
introduced in 3.14, and so per VERSIONS policy only an entry for
`compression` was added, and so we don't actually have any definite info
on `compression.zstd` and give up on producing a diagnostic. However the
`from compression import zstd` form fails on looking up `compression`
and we *do* have an exact match for that, so it gets a better
diagnostic!

(aside: I have now learned about the VERSIONS format and I *really* wish
they would just enumerate all the submodules but, oh well!)

The fix is, when handling an import failure, if we fail to find an exact
match *we requery with the parent module*. In cases like
`compression.zstd` this lets us at least identify that, hey, not even
`compression` exists, and luckily that fixes the whole issue. In cases
where the parent module and submodule were introduced at different times
then we may discover that the parent module is in-range and that's fine,
we don't produce the richer stdlib diagnostic.
2025-10-16 13:25:08 +00:00

4 KiB

Structures

Class import following

from b import C as D

E = D
reveal_type(E)  # revealed: <class 'C'>

b.py:

class C: ...

Module member resolution

import b

D = b.C
reveal_type(D)  # revealed: <class 'C'>

b.py:

class C: ...

Nested

import a.b

reveal_type(a.b.C)  # revealed: <class 'C'>

a/__init__.py:

a/b.py:

class C: ...

Deeply nested

import a.b.c

reveal_type(a.b.c.C)  # revealed: <class 'C'>

a/__init__.py:

a/b/__init__.py:

a/b/c.py:

class C: ...

Nested with rename

import a.b as b

reveal_type(b.C)  # revealed: <class 'C'>

a/__init__.py:

a/b.py:

class C: ...

Deeply nested with rename

import a.b.c as c

reveal_type(c.C)  # revealed: <class 'C'>

a/__init__.py:

a/b/__init__.py:

a/b/c.py:

class C: ...

Unresolvable module import

import zqzqzqzqzqzqzq  # error: [unresolved-import] "Cannot resolve imported module `zqzqzqzqzqzqzq`"

Unresolvable submodule imports

# Topmost component resolvable, submodule not resolvable:
import a.foo  # error: [unresolved-import] "Cannot resolve imported module `a.foo`"

# Topmost component unresolvable:
import b.foo  # error: [unresolved-import] "Cannot resolve imported module `b.foo`"

a/__init__.py:

Long paths

It's unlikely that a single module component is as long as in this example, but Windows treats paths that are longer than 200 and something specially. This test ensures that ty can handle those paths gracefully.

system = "os"

AveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPath/__init__.py:

class Foo: ...
from AveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPath import (
    Foo,
)

reveal_type(Foo())  # revealed: Foo

Multiple objects imported from an unresolved module

If multiple members are imported from a module that cannot be resolved, only a single diagnostic is emitted for the import from statement:

# error: [unresolved-import]
from does_not_exist import foo, bar, baz

Attempting to import a stdlib module that's not yet been added

[environment]
python-version = "3.10"
import tomllib  # error: [unresolved-import]
from string.templatelib import Template  # error: [unresolved-import]
from importlib.resources import abc  # error: [unresolved-import]

Attempting to import a stdlib submodule when both parts haven't yet been added

compression and compression.zstd were both added in 3.14 so there is a typeshed VERSIONS entry for compression but not compression.zstd. We can't be confident compression.zstd exists but we do know compression does and can still give good diagnostics about it.

[environment]
python-version = "3.10"
import compression.zstd  # error: [unresolved-import]
from compression import zstd  # error: [unresolved-import]
import compression.fakebutwhocansay  # error: [unresolved-import]
from compression import fakebutwhocansay  # error: [unresolved-import]

Attempting to import a stdlib module that was previously removed

[environment]
python-version = "3.13"
import aifc  # error: [unresolved-import]
from distutils import sysconfig  # error: [unresolved-import]

Cannot shadow core standard library modules

types.py:

x: int
# error: [unresolved-import]
from types import x

from types import FunctionType