Skip to content

Commit 8c87e6f

Browse files
committed
Only short-circuit on declared attributes
1 parent c7b9957 commit 8c87e6f

File tree

3 files changed

+38
-26
lines changed

3 files changed

+38
-26
lines changed

crates/ty_python_semantic/resources/mdtest/attributes.md

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,7 @@ reveal_type(Derived().redeclared_with_same_type) # revealed: str | None
891891

892892
# TODO: It would probably be more consistent if these were `str | None`
893893
reveal_type(Derived.redeclared_with_narrower_type) # revealed: str
894-
reveal_type(Derived().redeclared_with_narrower_type) # revealed: str
894+
reveal_type(Derived().redeclared_with_narrower_type) # revealed: str | None
895895

896896
# TODO: It would probably be more consistent if these were `str | None`
897897
reveal_type(Derived.redeclared_with_wider_type) # revealed: str | int | None
@@ -901,24 +901,21 @@ reveal_type(Derived().redeclared_with_wider_type) # revealed: str | int | None
901901
reveal_type(Derived.overwritten_in_subclass_body) # revealed: Unknown | None
902902
reveal_type(Derived().overwritten_in_subclass_body) # revealed: Unknown | None | str
903903

904-
# TODO: Both of these should be `str`
905904
reveal_type(Derived.overwritten_in_subclass_method) # revealed: str
906-
reveal_type(Derived().overwritten_in_subclass_method) # revealed: str | Unknown | None
905+
reveal_type(Derived().overwritten_in_subclass_method) # revealed: str
907906

908907
reveal_type(Derived().pure_attribute) # revealed: str | None
909908

910909
# TODO: This should be `str`
911910
reveal_type(Derived().pure_overwritten_in_subclass_body) # revealed: Unknown | None | str
912911

913-
# TODO: This should be `str`
914-
reveal_type(Derived().pure_overwritten_in_subclass_method) # revealed: Unknown | None
912+
reveal_type(Derived().pure_overwritten_in_subclass_method) # revealed: str
915913

916914
# TODO: Both of these should be `Unknown | Literal["intermediate", "base"]`
917915
reveal_type(Derived.undeclared) # revealed: Unknown | Literal["intermediate"]
918916
reveal_type(Derived().undeclared) # revealed: Unknown | Literal["intermediate"]
919917

920-
# TODO: This should be `Unknown | Literal["intermediate", "base"]`
921-
reveal_type(Derived().pure_undeclared) # revealed: Unknown | Literal["intermediate"]
918+
reveal_type(Derived().pure_undeclared) # revealed: Unknown | Literal["base", "intermediate"]
922919
```
923920

924921
## Accessing attributes on class objects

crates/ty_python_semantic/src/member.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{place::PlaceAndQualifiers, types::Type};
33
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
44
pub(crate) struct Member<'db> {
55
pub(crate) inner: PlaceAndQualifiers<'db>,
6-
pub(crate) declared: bool,
6+
pub(crate) is_declared: bool,
77
}
88

99
impl Default for Member<'_> {
@@ -16,14 +16,14 @@ impl<'db> Member<'db> {
1616
pub(crate) fn undeclared(inner: PlaceAndQualifiers<'db>) -> Self {
1717
Self {
1818
inner,
19-
declared: false,
19+
is_declared: false,
2020
}
2121
}
2222

2323
pub(crate) fn declared(inner: PlaceAndQualifiers<'db>) -> Self {
2424
Self {
2525
inner,
26-
declared: true,
26+
is_declared: true,
2727
}
2828
}
2929

@@ -39,7 +39,7 @@ impl<'db> Member<'db> {
3939
pub(crate) fn map_type(self, f: impl FnOnce(Type<'db>) -> Type<'db>) -> Self {
4040
Self {
4141
inner: self.inner.map_type(f),
42-
declared: self.declared,
42+
is_declared: self.is_declared,
4343
}
4444
}
4545
}

crates/ty_python_semantic/src/types/class.rs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ fn implicit_attribute_initial<'db>(
115115
) -> Member<'db> {
116116
Member {
117117
inner: Place::Unbound.into(),
118-
declared: false,
118+
is_declared: false,
119119
}
120120
}
121121

@@ -2871,8 +2871,12 @@ impl<'db> ClassLiteral<'db> {
28712871

28722872
let mut union = UnionBuilder::new(db);
28732873
let mut union_qualifiers = TypeQualifiers::empty();
2874+
let mut is_definitely_bound = false;
28742875

2875-
for superclass in self.iter_mro(db, specialization) {
2876+
let mro = self.try_mro(db, specialization);
2877+
let mro = mro.unwrap_or_else(|e| e.fallback_mro());
2878+
2879+
for superclass in mro.iter().rev() {
28762880
match superclass {
28772881
ClassBase::Generic | ClassBase::Protocol => {
28782882
// Skip over these very special class bases that aren't really classes.
@@ -2883,22 +2887,30 @@ impl<'db> ClassLiteral<'db> {
28832887
);
28842888
}
28852889
ClassBase::Class(class) => {
2886-
if let member @ PlaceAndQualifiers {
2887-
place: Place::Type(ty, boundness),
2888-
qualifiers,
2889-
} = class.own_instance_member(db, name).inner
2890+
if let Member {
2891+
inner:
2892+
member @ PlaceAndQualifiers {
2893+
place: Place::Type(ty, boundness),
2894+
qualifiers,
2895+
},
2896+
is_declared,
2897+
} = class.own_instance_member(db, name)
28902898
{
28912899
// TODO: We could raise a diagnostic here if there are conflicting type qualifiers
28922900
union_qualifiers |= qualifiers;
28932901

28942902
if boundness == Boundness::Bound {
2895-
if union.is_empty() {
2896-
// Short-circuit, no need to allocate inside the union builder
2897-
return member;
2898-
}
2903+
is_definitely_bound = true;
2904+
2905+
if is_declared {
2906+
if union.is_empty() {
2907+
// Short-circuit, no need to allocate inside the union builder
2908+
return member;
2909+
}
28992910

2900-
return Place::bound(union.add(ty).build())
2901-
.with_qualifiers(union_qualifiers);
2911+
return Place::bound(union.add(ty).build())
2912+
.with_qualifiers(union_qualifiers);
2913+
}
29022914
}
29032915

29042916
// If we see a possibly-unbound symbol, we need to keep looking
@@ -2928,10 +2940,13 @@ impl<'db> ClassLiteral<'db> {
29282940
if union.is_empty() {
29292941
Place::Unbound.with_qualifiers(TypeQualifiers::empty())
29302942
} else {
2931-
// If we have reached this point, we know that we have only seen possibly-unbound places.
2932-
// This means that the final result is still possibly-unbound.
2943+
let boundness = if is_definitely_bound {
2944+
Boundness::Bound
2945+
} else {
2946+
Boundness::PossiblyUnbound
2947+
};
29332948

2934-
Place::Type(union.build(), Boundness::PossiblyUnbound).with_qualifiers(union_qualifiers)
2949+
Place::Type(union.build(), boundness).with_qualifiers(union_qualifiers)
29352950
}
29362951
}
29372952

0 commit comments

Comments
 (0)