[ty] Fix __file__ type in completions to show str instead of str | None (#22510)

## Summary

The type inference system already correctly special-cases `__file__` to
return `str` for the current module (since the code is executing from an
existing file). However, the completion system was bypassing this logic
and pulling `__file__: str | None` directly from `types.ModuleType` in
typeshed.

This PR adds implicit module globals (like `__file__`, `__name__`, etc.)
with their correctly-typed values to completions, reusing the existing
`module_type_implicit_global_symbol` function that already handles the
special-casing.

Closes https://github.com/astral-sh/ty/issues/2445.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Charlie Marsh
2026-01-12 09:20:32 -05:00
committed by GitHub
parent 29064034ba
commit e4ba29392b
4 changed files with 80 additions and 1 deletions

View File

@@ -1583,7 +1583,7 @@ fn is_reexported(db: &dyn Db, definition: Definition<'_>) -> bool {
all_names.contains(symbol_name)
}
mod implicit_globals {
pub(crate) mod implicit_globals {
use ruff_python_ast as ast;
use ruff_python_ast::name::Name;
@@ -1763,6 +1763,32 @@ mod implicit_globals {
smallvec::SmallVec::default()
}
/// Returns an iterator over all implicit module global symbols and their types.
///
/// This is used for completions in the global scope of a module. It returns
/// the correct types for special-cased symbols like `__file__` (which is `str`
/// for the current module, not `str | None`).
pub(crate) fn all_implicit_module_globals(
db: &dyn Db,
) -> impl Iterator<Item = (Name, Type<'_>)> + '_ {
// Special-cased implicit globals that are not in `module_type_symbols`
let special_cased = ["__builtins__", "__debug__", "__warningregistry__"]
.into_iter()
.map(Name::new_static);
// All symbols from ModuleType (already includes `__file__`, `__name__`, etc.)
let module_type_syms = module_type_symbols(db).iter().cloned();
// Combine and map to (name, type) pairs
special_cased
.chain(module_type_syms)
.filter_map(move |name| {
let place = module_type_implicit_global_symbol(db, name.as_str());
// Only include bound symbols (not undefined or possibly-undefined)
place.place.ignore_possibly_undefined().map(|ty| (name, ty))
})
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -10,6 +10,7 @@ use ty_module_resolver::{
};
use crate::Db;
use crate::place::implicit_globals::all_implicit_module_globals;
use crate::semantic_index::definition::Definition;
use crate::semantic_index::scope::FileScopeId;
use crate::semantic_index::semantic_index;
@@ -234,6 +235,19 @@ impl<'db> SemanticModel<'db> {
),
);
}
// Add implicit module globals (like `__file__`, `__name__`, etc.) with their
// correct types. These are added before builtins so that the deduplication
// keeps the correct types (e.g., `__file__` is `str` for the current module,
// not `str | None`).
completions.extend(
all_implicit_module_globals(self.db).map(|(name, ty)| Completion {
name,
ty: Some(ty),
builtin: true,
}),
);
// Builtins are available in all scopes.
let builtins = ModuleName::new_static("builtins").expect("valid module name");
completions.extend(self.module_completions(&builtins));