mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:34:57 +00:00
[ty] Preserve qualifiers when accessing attributes on unions/intersections (#20114)
## Summary Properly preserve type qualifiers when accessing attributes on unions and intersections. This is a prerequisite for https://github.com/astral-sh/ruff/pull/19579. Also fix a completely wrong implementation of `map_with_boundness_and_qualifiers`. It now closely follows `map_with_boundness` (just above). ## Test Plan I thought about it, but didn't find any easy way to test this. This only affected `Type::member`. Things like validation of attribute writes (where type qualifiers like `ClassVar` and `Final` are important) were already handling things correctly.
This commit is contained in:
parent
ce1dc21e7e
commit
0b3548755c
2 changed files with 16 additions and 22 deletions
|
@ -398,7 +398,7 @@ def f_okay(c: Callable[[], None]):
|
||||||
c.__qualname__ = "my_callable"
|
c.__qualname__ = "my_callable"
|
||||||
|
|
||||||
result = getattr_static(c, "__qualname__")
|
result = getattr_static(c, "__qualname__")
|
||||||
reveal_type(result) # revealed: Never
|
reveal_type(result) # revealed: property
|
||||||
if isinstance(result, property) and result.fset:
|
if isinstance(result, property) and result.fset:
|
||||||
c.__qualname__ = "my_callable" # okay
|
c.__qualname__ = "my_callable" # okay
|
||||||
```
|
```
|
||||||
|
|
|
@ -3309,19 +3309,14 @@ impl<'db> Type<'db> {
|
||||||
let name_str = name.as_str();
|
let name_str = name.as_str();
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Type::Union(union) => union
|
Type::Union(union) => union.map_with_boundness_and_qualifiers(db, |elem| {
|
||||||
.map_with_boundness(db, |elem| {
|
elem.member_lookup_with_policy(db, name_str.into(), policy)
|
||||||
elem.member_lookup_with_policy(db, name_str.into(), policy)
|
}),
|
||||||
.place
|
|
||||||
})
|
|
||||||
.into(),
|
|
||||||
|
|
||||||
Type::Intersection(intersection) => intersection
|
Type::Intersection(intersection) => intersection
|
||||||
.map_with_boundness(db, |elem| {
|
.map_with_boundness_and_qualifiers(db, |elem| {
|
||||||
elem.member_lookup_with_policy(db, name_str.into(), policy)
|
elem.member_lookup_with_policy(db, name_str.into(), policy)
|
||||||
.place
|
}),
|
||||||
})
|
|
||||||
.into(),
|
|
||||||
|
|
||||||
Type::Dynamic(..) | Type::Never => Place::bound(self).into(),
|
Type::Dynamic(..) | Type::Never => Place::bound(self).into(),
|
||||||
|
|
||||||
|
@ -9743,8 +9738,8 @@ impl<'db> IntersectionType<'db> {
|
||||||
let mut builder = IntersectionBuilder::new(db);
|
let mut builder = IntersectionBuilder::new(db);
|
||||||
let mut qualifiers = TypeQualifiers::empty();
|
let mut qualifiers = TypeQualifiers::empty();
|
||||||
|
|
||||||
let mut any_unbound = false;
|
let mut all_unbound = true;
|
||||||
let mut any_possibly_unbound = false;
|
let mut any_definitely_bound = false;
|
||||||
for ty in self.positive_elements_or_object(db) {
|
for ty in self.positive_elements_or_object(db) {
|
||||||
let PlaceAndQualifiers {
|
let PlaceAndQualifiers {
|
||||||
place: member,
|
place: member,
|
||||||
|
@ -9752,12 +9747,11 @@ impl<'db> IntersectionType<'db> {
|
||||||
} = transform_fn(&ty);
|
} = transform_fn(&ty);
|
||||||
qualifiers |= new_qualifiers;
|
qualifiers |= new_qualifiers;
|
||||||
match member {
|
match member {
|
||||||
Place::Unbound => {
|
Place::Unbound => {}
|
||||||
any_unbound = true;
|
|
||||||
}
|
|
||||||
Place::Type(ty_member, member_boundness) => {
|
Place::Type(ty_member, member_boundness) => {
|
||||||
if member_boundness == Boundness::PossiblyUnbound {
|
all_unbound = false;
|
||||||
any_possibly_unbound = true;
|
if member_boundness == Boundness::Bound {
|
||||||
|
any_definitely_bound = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
builder = builder.add_positive(ty_member);
|
builder = builder.add_positive(ty_member);
|
||||||
|
@ -9766,15 +9760,15 @@ impl<'db> IntersectionType<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaceAndQualifiers {
|
PlaceAndQualifiers {
|
||||||
place: if any_unbound {
|
place: if all_unbound {
|
||||||
Place::Unbound
|
Place::Unbound
|
||||||
} else {
|
} else {
|
||||||
Place::Type(
|
Place::Type(
|
||||||
builder.build(),
|
builder.build(),
|
||||||
if any_possibly_unbound {
|
if any_definitely_bound {
|
||||||
Boundness::PossiblyUnbound
|
|
||||||
} else {
|
|
||||||
Boundness::Bound
|
Boundness::Bound
|
||||||
|
} else {
|
||||||
|
Boundness::PossiblyUnbound
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue