[red-knot] Improve handling of visibility constraints in external modules when resolving * imports (#17286)

This commit is contained in:
Alex Waygood 2025-04-09 15:36:52 +01:00 committed by GitHub
parent f1ba596f22
commit 6ec4c6a97e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 180 additions and 47 deletions

View file

@ -227,23 +227,23 @@ print((
D,
E,
F,
G, # TODO: could emit diagnostic about being possibly unbound
H,
G, # error: [possibly-unresolved-reference]
H, # error: [possibly-unresolved-reference]
I,
J,
K,
L,
M, # TODO: could emit diagnostic about being possibly unbound
N, # TODO: could emit diagnostic about being possibly unbound
O, # TODO: could emit diagnostic about being possibly unbound
P, # TODO: could emit diagnostic about being possibly unbound
Q, # TODO: could emit diagnostic about being possibly unbound
R, # TODO: could emit diagnostic about being possibly unbound
S, # TODO: could emit diagnostic about being possibly unbound
T, # TODO: could emit diagnostic about being possibly unbound
U, # TODO: could emit diagnostic about being possibly unbound
V, # TODO: could emit diagnostic about being possibly unbound
W, # TODO: could emit diagnostic about being possibly unbound
M, # error: [possibly-unresolved-reference]
N, # error: [possibly-unresolved-reference]
O, # error: [possibly-unresolved-reference]
P, # error: [possibly-unresolved-reference]
Q, # error: [possibly-unresolved-reference]
R, # error: [possibly-unresolved-reference]
S, # error: [possibly-unresolved-reference]
T, # error: [possibly-unresolved-reference]
U, # TODO: could emit [possibly-unresolved-reference here] (https://github.com/astral-sh/ruff/issues/16996)
V, # error: [possibly-unresolved-reference]
W, # error: [possibly-unresolved-reference]
typing,
OrderedDict,
Foo,
@ -479,15 +479,21 @@ reveal_type(s) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(t) # revealed: Unknown
# TODO: these should all reveal `Unknown | int`.
# TODO: these should all reveal `Unknown | int` and should not emit errors.
# (We don't generally model elsewhere in red-knot that bindings from walruses
# "leak" from comprehension scopes into outer scopes, but we should.)
# See https://github.com/astral-sh/ruff/issues/16954
# error: [unresolved-reference]
reveal_type(g) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(i) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(k) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(m) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(o) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(q) # revealed: Unknown
```
@ -668,15 +674,15 @@ from exporter import *
reveal_type(X) # revealed: bool
# TODO: should emit error: [unresolved-reference]
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
# TODO: The `*` import should not be considered a redefinition
# of the global variable in this module, as the symbol in
# The `*` import is not considered a redefinition
# of the global variable `Z` in this module, as the symbol in
# the `a` module is in a branch that is statically known
# to be dead code given the `python-version` configuration.
# Thus this should reveal `Literal[True]`.
reveal_type(Z) # revealed: Unknown
# Thus this still reveals `Literal[True]`.
reveal_type(Z) # revealed: Literal[True]
```
### Multiple `*` imports with always-false visibility constraints
@ -707,8 +713,7 @@ from exporter import *
from exporter import *
from exporter import *
# TODO: should still be `Literal[True]
reveal_type(Z) # revealed: Unknown
reveal_type(Z) # revealed: Literal[True]
```
### Ambiguous visibility constraints
@ -735,7 +740,7 @@ else:
```py
from exporter import *
# TODO: should have a `[possibly-unresolved-reference]` diagnostic
# error: [possibly-unresolved-reference]
reveal_type(A) # revealed: Unknown | Literal[1]
reveal_type(B) # revealed: Unknown | Literal[2, 3]
@ -798,16 +803,14 @@ if sys.version_info >= (3, 12):
else:
from exporter import *
# TODO: should have an `[unresolved-reference]` diagnostic
# error: [unresolved-reference]
reveal_type(A) # revealed: Unknown
# TODO: should have a `[possibly-unresolved-reference]` diagnostic
# error: [possibly-unresolved-reference]
reveal_type(B) # revealed: bool
# TODO: should have an `[unresolved-reference]` diagnostic
# error: [unresolved-reference]
reveal_type(A) # revealed: Unknown
# TODO: should have a `[possibly-unresolved-reference]` diagnostic
# error: [possibly-unresolved-reference]
reveal_type(B) # revealed: bool
```
@ -1065,7 +1068,7 @@ from exporter import *
reveal_type(X) # revealed: bool
reveal_type(Y) # revealed: bool
# TODO: should error with [unresolved-reference]
# error: [unresolved-reference]
reveal_type(Z) # revealed: Unknown
```
@ -1100,7 +1103,7 @@ from exporter import *
reveal_type(X) # revealed: bool
reveal_type(Y) # revealed: bool
# TODO should have an [unresolved-reference] diagnostic
# error: [unresolved-reference]
reveal_type(Z) # revealed: Unknown
```

View file

@ -196,10 +196,14 @@ reveal_type(c.attr) # revealed: Unknown
## Behind the scenes
> TODO: This test is currently disabled pending
> [an upstream Salsa fix](https://github.com/salsa-rs/salsa/pull/741). Once that has been merged,
> re-enable this test by changing the language codes below back to `py`.
In this section, we trace through some of the steps that make properties work. We start with a
simple class `C` and a property `attr`:
```py
```ignore
class C:
def __init__(self):
self._attr: int = 0
@ -216,7 +220,7 @@ class C:
Next, we create an instance of `C`. As we have seen above, accessing `attr` on the instance will
return an `int`:
```py
```ignore
c = C()
reveal_type(c.attr) # revealed: int
@ -226,7 +230,7 @@ Behind the scenes, when we write `c.attr`, the first thing that happens is that
up the symbol `attr` on the meta-type of `c`, i.e. the class `C`. We can emulate this static lookup
using `inspect.getattr_static`, to see that `attr` is actually an instance of the `property` class:
```py
```ignore
from inspect import getattr_static
attr_property = getattr_static(C, "attr")
@ -237,7 +241,7 @@ The `property` class has a `__get__` method, which makes it a descriptor. It als
method, which means that it is a *data* descriptor (if there is no setter, `__set__` is still
available but yields an `AttributeError` at runtime).
```py
```ignore
reveal_type(type(attr_property).__get__) # revealed: <wrapper-descriptor `__get__` of `property` objects>
reveal_type(type(attr_property).__set__) # revealed: <wrapper-descriptor `__set__` of `property` objects>
```
@ -246,14 +250,14 @@ When we access `c.attr`, the `__get__` method of the `property` class is called,
property object itself as the first argument, and the class instance `c` as the second argument. The
third argument is the "owner" which can be set to `None` or to `C` in this case:
```py
```ignore
reveal_type(type(attr_property).__get__(attr_property, c, C)) # revealed: int
reveal_type(type(attr_property).__get__(attr_property, c, None)) # revealed: int
```
Alternatively, the above can also be written as a method call:
```py
```ignore
reveal_type(attr_property.__get__(c, C)) # revealed: int
```
@ -261,7 +265,7 @@ When we access `attr` on the class itself, the descriptor protocol is also invok
argument is set to `None`. When `instance` is `None`, the call to `property.__get__` returns the
property instance itself. So the following expressions are all equivalent
```py
```ignore
reveal_type(attr_property) # revealed: property
reveal_type(C.attr) # revealed: property
reveal_type(attr_property.__get__(None, C)) # revealed: property
@ -271,7 +275,7 @@ reveal_type(type(attr_property).__get__(attr_property, None, C)) # revealed: pr
When we set the property using `c.attr = "a"`, the `__set__` method of the property class is called.
This attribute access desugars to
```py
```ignore
type(attr_property).__set__(attr_property, c, "a")
# error: [call-non-callable] "Call of wrapper descriptor `property.__set__` failed: calling the setter failed"
@ -280,7 +284,7 @@ type(attr_property).__set__(attr_property, c, 1)
which is also equivalent to the following expressions:
```py
```ignore
attr_property.__set__(c, "a")
# error: [call-non-callable]
attr_property.__set__(c, 1)
@ -293,7 +297,7 @@ C.attr.__set__(c, 1)
Properties also have `fget` and `fset` attributes that can be used to retrieve the original getter
and setter functions, respectively.
```py
```ignore
reveal_type(attr_property.fget) # revealed: Literal[attr]
reveal_type(attr_property.fget(c)) # revealed: int