diff --git a/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md b/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md index 71c298b8d9b25..09fdefa94278e 100644 --- a/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md +++ b/crates/ty_python_semantic/resources/mdtest/scopes/moduletype_attrs.md @@ -8,7 +8,8 @@ is unbound. ```py reveal_type(__name__) # revealed: str -reveal_type(__file__) # revealed: str | None +# Typeshed says this is str | None, but for a pure-Python on-disk module its always str +reveal_type(__file__) # revealed: str reveal_type(__loader__) # revealed: LoaderProtocol | None reveal_type(__package__) # revealed: str | None reveal_type(__doc__) # revealed: str | None @@ -52,6 +53,10 @@ import typing reveal_type(typing.__name__) # revealed: str reveal_type(typing.__init__) # revealed: bound method ModuleType.__init__(name: str, doc: str | None = ellipsis) -> None +# For a stub module, we don't know that `__file__` is a string (at runtime it may be entirely +# unset, but we follow typeshed here): +reveal_type(typing.__file__) # revealed: str | None + # These come from `builtins.object`, not `types.ModuleType`: reveal_type(typing.__eq__) # revealed: bound method ModuleType.__eq__(value: object, /) -> bool diff --git a/crates/ty_python_semantic/src/symbol.rs b/crates/ty_python_semantic/src/symbol.rs index 920c7ff9e65fa..b18dba7733228 100644 --- a/crates/ty_python_semantic/src/symbol.rs +++ b/crates/ty_python_semantic/src/symbol.rs @@ -982,12 +982,18 @@ mod implicit_globals { db: &'db dyn Db, name: &str, ) -> SymbolAndQualifiers<'db> { + // We special-case `__file__` here because we know that for an internal implicit global + // lookup in a Python module, it is always a string, even though typeshed says `str | + // None`. + if name == "__file__" { + Symbol::bound(KnownClass::Str.to_instance(db)).into() + } // In general we wouldn't check to see whether a symbol exists on a class before doing the // `.member()` call on the instance type -- we'd just do the `.member`() call on the instance // type, since it has the same end result. The reason to only call `.member()` on `ModuleType` // when absolutely necessary is that this function is used in a very hot path (name resolution // in `infer.rs`). We use less idiomatic (and much more verbose) code here as a micro-optimisation. - if module_type_symbols(db) + else if module_type_symbols(db) .iter() .any(|module_type_member| &**module_type_member == name) {