mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-30 08:23:53 +00:00
[red-knot] Add tests for member lookup on union types (#14296)
## Summary - Write tests for member lookups on union types - Remove TODO comment part of: #14022 ## Test Plan New MD tests
This commit is contained in:
parent
13a1483f1e
commit
907047bf4b
2 changed files with 84 additions and 4 deletions
|
@ -9,14 +9,21 @@ def bool_instance() -> bool:
|
||||||
flag = bool_instance()
|
flag = bool_instance()
|
||||||
|
|
||||||
if flag:
|
if flag:
|
||||||
class C:
|
class C1:
|
||||||
x = 1
|
x = 1
|
||||||
|
|
||||||
else:
|
else:
|
||||||
class C:
|
class C1:
|
||||||
x = 2
|
x = 2
|
||||||
|
|
||||||
reveal_type(C.x) # revealed: Literal[1, 2]
|
class C2:
|
||||||
|
if flag:
|
||||||
|
x = 3
|
||||||
|
else:
|
||||||
|
x = 4
|
||||||
|
|
||||||
|
reveal_type(C1.x) # revealed: Literal[1, 2]
|
||||||
|
reveal_type(C2.x) # revealed: Literal[3, 4]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Inherited attributes
|
## Inherited attributes
|
||||||
|
@ -53,3 +60,77 @@ reveal_type(A.__mro__)
|
||||||
# `E` is earlier in the MRO than `F`, so we should use the type of `E.X`
|
# `E` is earlier in the MRO than `F`, so we should use the type of `E.X`
|
||||||
reveal_type(A.X) # revealed: Literal[42]
|
reveal_type(A.X) # revealed: Literal[42]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Unions with possibly unbound paths
|
||||||
|
|
||||||
|
### Definite boundness within a class
|
||||||
|
|
||||||
|
In this example, the `x` attribute is not defined in the `C2` element of the union:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def bool_instance() -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
class C1:
|
||||||
|
x = 1
|
||||||
|
|
||||||
|
class C2: ...
|
||||||
|
|
||||||
|
class C3:
|
||||||
|
x = 3
|
||||||
|
|
||||||
|
flag1 = bool_instance()
|
||||||
|
flag2 = bool_instance()
|
||||||
|
|
||||||
|
C = C1 if flag1 else C2 if flag2 else C3
|
||||||
|
|
||||||
|
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
|
||||||
|
reveal_type(C.x) # revealed: Literal[1, 3]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Possibly-unbound within a class
|
||||||
|
|
||||||
|
We raise the same diagnostic if the attribute is possibly-unbound in at least one element of the
|
||||||
|
union:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def bool_instance() -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
class C1:
|
||||||
|
x = 1
|
||||||
|
|
||||||
|
class C2:
|
||||||
|
if bool_instance():
|
||||||
|
x = 2
|
||||||
|
|
||||||
|
class C3:
|
||||||
|
x = 3
|
||||||
|
|
||||||
|
flag1 = bool_instance()
|
||||||
|
flag2 = bool_instance()
|
||||||
|
|
||||||
|
C = C1 if flag1 else C2 if flag2 else C3
|
||||||
|
|
||||||
|
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
|
||||||
|
reveal_type(C.x) # revealed: Literal[1, 2, 3]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unions with all paths unbound
|
||||||
|
|
||||||
|
If the symbol is unbound in all elements of the union, we detect that:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def bool_instance() -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
class C1: ...
|
||||||
|
class C2: ...
|
||||||
|
|
||||||
|
flag = bool_instance()
|
||||||
|
|
||||||
|
C = C1 if flag else C2
|
||||||
|
|
||||||
|
# error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`"
|
||||||
|
reveal_type(C.x) # revealed: Unknown
|
||||||
|
```
|
||||||
|
|
|
@ -1108,7 +1108,6 @@ impl<'db> Type<'db> {
|
||||||
possibly_unbound = true;
|
possibly_unbound = true;
|
||||||
}
|
}
|
||||||
Symbol::Type(ty_member, member_boundness) => {
|
Symbol::Type(ty_member, member_boundness) => {
|
||||||
// TODO: raise a diagnostic if member_boundness indicates potential unboundness
|
|
||||||
if member_boundness == Boundness::PossiblyUnbound {
|
if member_boundness == Boundness::PossiblyUnbound {
|
||||||
possibly_unbound = true;
|
possibly_unbound = true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue