[red-knot] Diagnostic for possibly unbound imports (#14281)

## Summary

This adds a new diagnostic when possibly unbound symbols are imported.
The `TODO` comment had a question mark, do I'm not sure if this is
really something that we want.

This does not touch the un*declared* case, yet.

relates to: #14022

## Test Plan

Updated already existing tests with new diagnostics
This commit is contained in:
David Peter 2024-11-11 20:26:01 +01:00 committed by GitHub
parent f82ee8ea59
commit 3bef23669f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 20 additions and 10 deletions

View file

@ -21,6 +21,7 @@ reveal_type(y)
```
```py
# error: [possibly-unbound-import] "Member `y` of module `maybe_unbound` is possibly unbound"
from maybe_unbound import x, y
reveal_type(x) # revealed: Literal[3]
@ -50,6 +51,7 @@ reveal_type(y)
Importing an annotated name prefers the declared type over the inferred type:
```py
# error: [possibly-unbound-import] "Member `y` of module `maybe_unbound_annotated` is possibly unbound"
from maybe_unbound_annotated import x, y
reveal_type(x) # revealed: Literal[3]

View file

@ -102,6 +102,9 @@ else:
### Handling of `None`
```py
# TODO: this error should ideally go away once we (1) understand `sys.version_info` branches,
# and (2) set the target Python version for this test to 3.10.
# error: [possibly-unbound-import] "Member `NoneType` of module `types` is possibly unbound"
from types import NoneType
def flag() -> bool: ...

View file

@ -10,8 +10,6 @@ reveal_type(not not None) # revealed: Literal[False]
## Function
```py
from typing import reveal_type
def f():
return 1

View file

@ -2008,20 +2008,27 @@ impl<'db> TypeInferenceBuilder<'db> {
asname: _,
} = alias;
// For possibly-unbound names, just eliminate Unbound from the type; we
// must be in a bound path. TODO diagnostic for possibly-unbound import?
module_ty
.member(self.db, &ast::name::Name::new(&name.id))
.ignore_possibly_unbound()
.unwrap_or_else(|| {
match module_ty.member(self.db, &ast::name::Name::new(&name.id)) {
Symbol::Type(ty, boundness) => {
if boundness == Boundness::PossiblyUnbound {
self.diagnostics.add(
AnyNodeRef::Alias(alias),
"possibly-unbound-import",
format_args!("Member `{name}` of module `{module_name}` is possibly unbound",),
);
}
ty
}
Symbol::Unbound => {
self.diagnostics.add(
AnyNodeRef::Alias(alias),
"unresolved-import",
format_args!("Module `{module_name}` has no member `{name}`",),
);
Type::Unknown
})
}
}
} else {
self.diagnostics
.add_unresolved_module(import_from, *level, module);