astral-sh/ruff · critical

File name should be non-null because path is guaranteed to b

Error message

File name should be non-null because path is guaranteed to be a child of `{prefix_dir}`

What it means

While enumerating site-packages directories, ty reads each subdirectory's file name with `Path::file_name()`. Because the entries come from reading a directory directly under a known `prefix_dir`, the file name is expected to always exist; if it does not, this invariant is broken and the code panics. In practice this can occur with odd paths such as entries whose path ends in `..` or filesystem oddities.

Source

Thrown at crates/ty_site_packages/src/lib.rs:1589

    prefix_dir: &SystemPath,
    suffixes: &(impl IntoIterator<Item = InstallationDir> + Clone),
    implementation: PythonImplementation,
    system: &dyn System,
    settings_diagnostic_path: Option<&SystemPath>,
    directories: &mut SitePackagesPaths,
) {
    let Ok(dir_iter) = system.read_directory(prefix_dir) else {
        return;
    };

    for entry_result in dir_iter {
        let Ok(entry) = entry_result else { continue };
        if !entry.file_type().is_directory() {
            continue;
        }
        let path = entry.into_path();
        let name = path.file_name().unwrap_or_else(|| {
            panic!(
                "File name should be non-null because path is guaranteed \
                to be a child of `{prefix_dir}`"
            )
        });

        let matches_implementation = match implementation {
            PythonImplementation::CPython | PythonImplementation::GraalPy => {
                name.starts_with("python3.")
            }
            PythonImplementation::PyPy => name.starts_with("pypy3."),
            PythonImplementation::Unknown => {
                name.starts_with("python3.") || name.starts_with("pypy3.")
            }
        };

        if matches_implementation {
            for suffix in suffixes.clone() {
                let candidate = path.join(suffix.as_str());

View on GitHub (pinned to 26f38c119c)

Solutions

  1. Inspect the site-packages directory for malformed or odd-named entries and remove/repair them.
  2. Recreate the virtual environment to restore a clean site-packages layout.
  3. Point ty's interpreter/python-path setting at a standard virtualenv or conda environment with a conventional layout.
Defensive patterns

Strategy: fallback

Validate before calling

# Before pointing ty at a prefix, sanity-check its site-packages layout:
import pathlib
for d in pathlib.Path(site_packages).iterdir():
    if d.is_dir() and d.name == '':
        print('malformed entry:', d)

Prevention

When it happens

Trigger: Scanning site-packages where a directory entry's path has no final component (file_name() returns None) — e.g. the read_dir root resolving oddly, symlinked or malformed paths under the prefix directory, so the unwrap_or_else panic fires.

Common situations: Nonstandard PYTHONPATH/site-packages layouts, broken symlinks or entries created by package managers leaving odd directory names, running ty against an unusual prefix (e.g. a mount point or root) where an entry path normalizes away its file name.

Related errors


AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05). Data as JSON: /api/errors/9a536ccc13cd88d0. Report an issue: GitHub.