mirror of
https://github.com/astral-sh/ruff
synced 2026-01-21 05:20:49 -05:00
[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:
@@ -8059,6 +8059,17 @@ def f(x: Intersection[int, Any] | str):
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dunder_file_completion() {
|
||||
let builder = completion_test_builder("__fil<CURSOR>");
|
||||
|
||||
// __file__ should be `str` when accessed within a module, not `str | None`
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_auto_import().type_signatures().build().snapshot(),
|
||||
@"__file__ :: str",
|
||||
);
|
||||
}
|
||||
|
||||
/// A way to create a simple single-file (named `main.py`) completion test
|
||||
/// builder.
|
||||
///
|
||||
|
||||
@@ -4166,6 +4166,34 @@ def function():
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_dunder_file() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
__fil<CURSOR>e__
|
||||
"#,
|
||||
);
|
||||
|
||||
// __file__ should be `str` when accessed within a module, not `str | None`
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
str
|
||||
---------------------------------------------
|
||||
```python
|
||||
str
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:1
|
||||
|
|
||||
2 | __file__
|
||||
| ^^^^^-^^
|
||||
| | |
|
||||
| | Cursor offset
|
||||
| source
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
impl CursorTest {
|
||||
fn hover(&self) -> String {
|
||||
use std::fmt::Write;
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user