[red-knot] Trust all symbols in stub files (#17588)
## Summary *Generally* trust undeclared symbols in stubs, not just at the module level. Follow-up on the discussion [here](https://github.com/astral-sh/ruff/pull/17577#discussion_r2055945909). ## Test Plan New Markdown test.
This commit is contained in:
@@ -292,3 +292,66 @@ reveal_type(a) # revealed: Unknown
|
||||
# Modifications allowed in this case:
|
||||
a = None
|
||||
```
|
||||
|
||||
## In stub files
|
||||
|
||||
In stub files, we have a minor modification to the rules above: we do not union with `Unknown` for
|
||||
undeclared symbols.
|
||||
|
||||
### Undeclared and bound
|
||||
|
||||
`mod.pyi`:
|
||||
|
||||
```pyi
|
||||
MyInt = int
|
||||
|
||||
class C:
|
||||
MyStr = str
|
||||
```
|
||||
|
||||
```py
|
||||
from mod import MyInt, C
|
||||
|
||||
reveal_type(MyInt) # revealed: Literal[int]
|
||||
reveal_type(C.MyStr) # revealed: Literal[str]
|
||||
```
|
||||
|
||||
### Undeclared and possibly unbound
|
||||
|
||||
`mod.pyi`:
|
||||
|
||||
```pyi
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
if flag():
|
||||
MyInt = int
|
||||
|
||||
class C:
|
||||
MyStr = str
|
||||
```
|
||||
|
||||
```py
|
||||
# error: [possibly-unbound-import]
|
||||
# error: [possibly-unbound-import]
|
||||
from mod import MyInt, C
|
||||
|
||||
reveal_type(MyInt) # revealed: Literal[int]
|
||||
reveal_type(C.MyStr) # revealed: Literal[str]
|
||||
```
|
||||
|
||||
### Undeclared and unbound
|
||||
|
||||
`mod.pyi`:
|
||||
|
||||
```pyi
|
||||
if False:
|
||||
MyInt = int
|
||||
```
|
||||
|
||||
```py
|
||||
# error: [unresolved-import]
|
||||
from mod import MyInt
|
||||
|
||||
reveal_type(MyInt) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -115,10 +115,6 @@ impl<'db> ScopeId<'db> {
|
||||
self.node(db).scope_kind().is_function_like()
|
||||
}
|
||||
|
||||
pub(crate) fn is_module_scope(self, db: &'db dyn Db) -> bool {
|
||||
self.node(db).scope_kind().is_module()
|
||||
}
|
||||
|
||||
pub(crate) fn is_type_parameter(self, db: &'db dyn Db) -> bool {
|
||||
self.node(db).scope_kind().is_type_parameter()
|
||||
}
|
||||
@@ -267,10 +263,6 @@ impl ScopeKind {
|
||||
matches!(self, ScopeKind::Class)
|
||||
}
|
||||
|
||||
pub(crate) fn is_module(self) -> bool {
|
||||
matches!(self, ScopeKind::Module)
|
||||
}
|
||||
|
||||
pub(crate) fn is_type_parameter(self) -> bool {
|
||||
matches!(self, ScopeKind::Annotation | ScopeKind::TypeAlias)
|
||||
}
|
||||
|
||||
@@ -593,7 +593,7 @@ fn symbol_by_id<'db>(
|
||||
"__slots__" | "TYPE_CHECKING"
|
||||
);
|
||||
|
||||
if scope.is_module_scope(db) && scope.file(db).is_stub(db.upcast()) {
|
||||
if scope.file(db).is_stub(db.upcast()) {
|
||||
// We generally trust module-level undeclared symbols in stubs and do not union
|
||||
// with `Unknown`. If we don't do this, simple aliases like `IOError = OSError` in
|
||||
// stubs would result in `IOError` being a union of `OSError` and `Unknown`, which
|
||||
|
||||
Reference in New Issue
Block a user