Compare commits
2 Commits
gankra/nam
...
charlie/ty
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44a938a78a | ||
|
|
3aa13b7d32 |
@@ -476,7 +476,12 @@ impl<'db> BoundSuperType<'db> {
|
||||
},
|
||||
Type::SpecialForm(SpecialFormType::Protocol) => ClassBase::Protocol,
|
||||
Type::SpecialForm(SpecialFormType::Generic) => ClassBase::Generic,
|
||||
Type::SpecialForm(SpecialFormType::TypedDict) => ClassBase::TypedDict,
|
||||
Type::SpecialForm(SpecialFormType::TypedDict) => {
|
||||
match KnownClass::Dict.to_class_literal(db).to_class_type(db) {
|
||||
Some(dict_class) => ClassBase::Class(dict_class),
|
||||
None => ClassBase::unknown(),
|
||||
}
|
||||
}
|
||||
Type::Dynamic(dynamic) => ClassBase::Dynamic(dynamic),
|
||||
_ => {
|
||||
return Err(BoundSuperError::InvalidPivotClassType {
|
||||
@@ -491,7 +496,7 @@ impl<'db> BoundSuperType<'db> {
|
||||
let pivot_class = pivot_class.class_literal(db).0;
|
||||
if !owner_class.iter_mro(db).any(|superclass| match superclass {
|
||||
ClassBase::Dynamic(_) => true,
|
||||
ClassBase::Generic | ClassBase::Protocol | ClassBase::TypedDict => false,
|
||||
ClassBase::Generic | ClassBase::Protocol => false,
|
||||
ClassBase::Class(superclass) => superclass.class_literal(db).0 == pivot_class,
|
||||
}) {
|
||||
return Err(BoundSuperError::FailingConditionCheck {
|
||||
|
||||
@@ -645,10 +645,8 @@ impl<'db> ClassType<'db> {
|
||||
}
|
||||
},
|
||||
|
||||
// Protocol, Generic, and TypedDict are not represented by a ClassType.
|
||||
ClassBase::Protocol | ClassBase::Generic | ClassBase::TypedDict => {
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
// Protocol and Generic are not represented by a ClassType.
|
||||
ClassBase::Protocol | ClassBase::Generic => ConstraintSet::from(false),
|
||||
|
||||
ClassBase::Class(base) => match (base, other) {
|
||||
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => {
|
||||
@@ -1961,8 +1959,15 @@ impl<'db> ClassLiteral<'db> {
|
||||
return known.is_typed_dict_subclass();
|
||||
}
|
||||
|
||||
self.iter_mro(db, None)
|
||||
.any(|base| matches!(base, ClassBase::TypedDict))
|
||||
self.explicit_bases(db).iter().any(|base| {
|
||||
if matches!(base, Type::SpecialForm(SpecialFormType::TypedDict)) {
|
||||
return true;
|
||||
}
|
||||
if let Some(class) = base.to_class_type(db) {
|
||||
return class.class_literal(db).0.is_typed_dict(db);
|
||||
}
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute `TypedDict` parameters dynamically based on MRO detection and AST parsing.
|
||||
@@ -2204,6 +2209,32 @@ impl<'db> ClassLiteral<'db> {
|
||||
ClassBase::Class(class) => {
|
||||
let known = class.known(db);
|
||||
|
||||
// For TypedDicts, when we reach the `dict` base, fall back to TypedDictFallback.
|
||||
if known == Some(KnownClass::Dict) && self.is_typed_dict(db) {
|
||||
return KnownClass::TypedDictFallback
|
||||
.to_class_literal(db)
|
||||
.find_name_in_mro_with_policy(db, name, policy)
|
||||
.expect("Will return Some() when called on class literal")
|
||||
.map_type(|ty| {
|
||||
ty.apply_type_mapping(
|
||||
db,
|
||||
&TypeMapping::ReplaceSelf {
|
||||
new_upper_bound: determine_upper_bound(
|
||||
db,
|
||||
self,
|
||||
None,
|
||||
|base| {
|
||||
base.into_class().is_some_and(|c| {
|
||||
c.is_known(db, KnownClass::Dict)
|
||||
})
|
||||
},
|
||||
),
|
||||
},
|
||||
TypeContext::default(),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
if known == Some(KnownClass::Object)
|
||||
// Only exclude `object` members if this is not an `object` class itself
|
||||
&& (policy.mro_no_object_fallback() && !self.is_known(db, KnownClass::Object))
|
||||
@@ -2230,26 +2261,6 @@ impl<'db> ClassLiteral<'db> {
|
||||
)
|
||||
});
|
||||
}
|
||||
ClassBase::TypedDict => {
|
||||
return KnownClass::TypedDictFallback
|
||||
.to_class_literal(db)
|
||||
.find_name_in_mro_with_policy(db, name, policy)
|
||||
.expect("Will return Some() when called on class literal")
|
||||
.map_type(|ty| {
|
||||
ty.apply_type_mapping(
|
||||
db,
|
||||
&TypeMapping::ReplaceSelf {
|
||||
new_upper_bound: determine_upper_bound(
|
||||
db,
|
||||
self,
|
||||
None,
|
||||
ClassBase::is_typed_dict,
|
||||
),
|
||||
},
|
||||
TypeContext::default(),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
if lookup_result.is_ok() {
|
||||
break;
|
||||
@@ -3169,20 +3180,18 @@ impl<'db> ClassLiteral<'db> {
|
||||
.to_class_literal(db)
|
||||
.find_name_in_mro_with_policy(db, name, policy)
|
||||
.expect("`find_name_in_mro_with_policy` will return `Some()` when called on class literal")
|
||||
.map_type(|ty|
|
||||
.map_type(|ty| {
|
||||
ty.apply_type_mapping(
|
||||
db,
|
||||
&TypeMapping::ReplaceSelf {
|
||||
new_upper_bound: determine_upper_bound(
|
||||
db,
|
||||
self,
|
||||
specialization,
|
||||
ClassBase::is_typed_dict
|
||||
)
|
||||
new_upper_bound: determine_upper_bound(db, self, specialization, |base| {
|
||||
base.into_class()
|
||||
.is_some_and(|c| c.is_known(db, KnownClass::Dict))
|
||||
}),
|
||||
},
|
||||
TypeContext::default(),
|
||||
TypeContext::default(),
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3406,13 +3415,10 @@ impl<'db> ClassLiteral<'db> {
|
||||
specialization: Option<Specialization<'db>>,
|
||||
name: &str,
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
if self.is_typed_dict(db) {
|
||||
return Place::Undefined.into();
|
||||
}
|
||||
|
||||
let mut union = UnionBuilder::new(db);
|
||||
let mut union_qualifiers = TypeQualifiers::empty();
|
||||
let mut is_definitely_bound = false;
|
||||
let mut found_declared = false;
|
||||
|
||||
for superclass in self.iter_mro(db, specialization) {
|
||||
match superclass {
|
||||
@@ -3434,12 +3440,18 @@ impl<'db> ClassLiteral<'db> {
|
||||
if origin.is_declared() {
|
||||
// We found a definitely-declared attribute. Discard possibly collected
|
||||
// inferred types from subclasses and return the declared type.
|
||||
// But first check if this is a TypedDict; its fields are accessed
|
||||
// through the TypedDict type machinery, not instance_member.
|
||||
if self.is_typed_dict(db) {
|
||||
return Place::Undefined.into();
|
||||
}
|
||||
return member;
|
||||
}
|
||||
|
||||
is_definitely_bound = true;
|
||||
}
|
||||
|
||||
found_declared = true;
|
||||
// If the attribute is not definitely declared on this class, keep looking higher
|
||||
// up in the MRO, and build a union of all inferred types (and possibly-declared
|
||||
// types):
|
||||
@@ -3449,29 +3461,18 @@ impl<'db> ClassLiteral<'db> {
|
||||
union_qualifiers |= qualifiers;
|
||||
}
|
||||
}
|
||||
ClassBase::TypedDict => {
|
||||
return KnownClass::TypedDictFallback
|
||||
.to_instance(db)
|
||||
.instance_member(db, name)
|
||||
.map_type(|ty| {
|
||||
ty.apply_type_mapping(
|
||||
db,
|
||||
&TypeMapping::ReplaceSelf {
|
||||
new_upper_bound: Type::instance(
|
||||
db,
|
||||
self.unknown_specialization(db),
|
||||
),
|
||||
},
|
||||
TypeContext::default(),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if union.is_empty() {
|
||||
Place::Undefined.with_qualifiers(TypeQualifiers::empty())
|
||||
} else {
|
||||
// Check if this is a TypedDict; its fields are accessed through the
|
||||
// TypedDict type machinery, not instance_member.
|
||||
if found_declared && self.is_typed_dict(db) {
|
||||
return Place::Undefined.into();
|
||||
}
|
||||
|
||||
let boundness = if is_definitely_bound {
|
||||
Definedness::AlwaysDefined
|
||||
} else {
|
||||
|
||||
@@ -27,7 +27,6 @@ pub enum ClassBase<'db> {
|
||||
/// but nonetheless appears in the MRO of classes that inherit from `Generic[T]`,
|
||||
/// `Protocol[T]`, or bare `Protocol`.
|
||||
Generic,
|
||||
TypedDict,
|
||||
}
|
||||
|
||||
impl<'db> ClassBase<'db> {
|
||||
@@ -39,7 +38,7 @@ impl<'db> ClassBase<'db> {
|
||||
match self {
|
||||
Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()),
|
||||
Self::Class(class) => Self::Class(class.normalized_impl(db, visitor)),
|
||||
Self::Protocol | Self::Generic | Self::TypedDict => self,
|
||||
Self::Protocol | Self::Generic => self,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +53,7 @@ impl<'db> ClassBase<'db> {
|
||||
Self::Class(class) => Some(Self::Class(
|
||||
class.recursive_type_normalized_impl(db, div, nested)?,
|
||||
)),
|
||||
Self::Protocol | Self::Generic | Self::TypedDict => Some(self),
|
||||
Self::Protocol | Self::Generic => Some(self),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +68,6 @@ impl<'db> ClassBase<'db> {
|
||||
ClassBase::Dynamic(DynamicType::Divergent(_)) => "Divergent",
|
||||
ClassBase::Protocol => "Protocol",
|
||||
ClassBase::Generic => "Generic",
|
||||
ClassBase::TypedDict => "TypedDict",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +79,6 @@ impl<'db> ClassBase<'db> {
|
||||
.map_or(Self::unknown(), Self::Class)
|
||||
}
|
||||
|
||||
pub(super) const fn is_typed_dict(self) -> bool {
|
||||
matches!(self, ClassBase::TypedDict)
|
||||
}
|
||||
|
||||
/// Attempt to resolve `ty` into a `ClassBase`.
|
||||
///
|
||||
/// Return `None` if `ty` is not an acceptable type for a class base.
|
||||
@@ -295,7 +289,9 @@ impl<'db> ClassBase<'db> {
|
||||
SpecialFormType::OrderedDict => {
|
||||
Self::try_from_type(db, KnownClass::OrderedDict.to_class_literal(db), subclass)
|
||||
}
|
||||
SpecialFormType::TypedDict => Some(Self::TypedDict),
|
||||
SpecialFormType::TypedDict => {
|
||||
Self::try_from_type(db, KnownClass::Dict.to_class_literal(db), subclass)
|
||||
}
|
||||
SpecialFormType::Callable => Self::try_from_type(
|
||||
db,
|
||||
todo_type!("Support for Callable as a base class"),
|
||||
@@ -308,7 +304,7 @@ impl<'db> ClassBase<'db> {
|
||||
pub(super) fn into_class(self) -> Option<ClassType<'db>> {
|
||||
match self {
|
||||
Self::Class(class) => Some(class),
|
||||
Self::Dynamic(_) | Self::Generic | Self::Protocol | Self::TypedDict => None,
|
||||
Self::Dynamic(_) | Self::Generic | Self::Protocol => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +319,7 @@ impl<'db> ClassBase<'db> {
|
||||
Self::Class(class) => {
|
||||
Self::Class(class.apply_type_mapping_impl(db, type_mapping, tcx, visitor))
|
||||
}
|
||||
Self::Dynamic(_) | Self::Generic | Self::Protocol | Self::TypedDict => self,
|
||||
Self::Dynamic(_) | Self::Generic | Self::Protocol => self,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,10 +361,7 @@ impl<'db> ClassBase<'db> {
|
||||
.try_mro(db, specialization)
|
||||
.is_err_and(MroError::is_cycle)
|
||||
}
|
||||
ClassBase::Dynamic(_)
|
||||
| ClassBase::Generic
|
||||
| ClassBase::Protocol
|
||||
| ClassBase::TypedDict => false,
|
||||
ClassBase::Dynamic(_) | ClassBase::Generic | ClassBase::Protocol => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,9 +373,7 @@ impl<'db> ClassBase<'db> {
|
||||
) -> impl Iterator<Item = ClassBase<'db>> {
|
||||
match self {
|
||||
ClassBase::Protocol => ClassBaseMroIterator::length_3(db, self, ClassBase::Generic),
|
||||
ClassBase::Dynamic(_) | ClassBase::Generic | ClassBase::TypedDict => {
|
||||
ClassBaseMroIterator::length_2(db, self)
|
||||
}
|
||||
ClassBase::Dynamic(_) | ClassBase::Generic => ClassBaseMroIterator::length_2(db, self),
|
||||
ClassBase::Class(class) => {
|
||||
ClassBaseMroIterator::from_class(db, class, additional_specialization)
|
||||
}
|
||||
@@ -402,7 +393,6 @@ impl<'db> ClassBase<'db> {
|
||||
ClassBase::Class(class) => Type::from(class).display(self.db).fmt(f),
|
||||
ClassBase::Protocol => f.write_str("typing.Protocol"),
|
||||
ClassBase::Generic => f.write_str("typing.Generic"),
|
||||
ClassBase::TypedDict => f.write_str("typing.TypedDict"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,7 +414,6 @@ impl<'db> From<ClassBase<'db>> for Type<'db> {
|
||||
ClassBase::Class(class) => class.into(),
|
||||
ClassBase::Protocol => Type::SpecialForm(SpecialFormType::Protocol),
|
||||
ClassBase::Generic => Type::SpecialForm(SpecialFormType::Generic),
|
||||
ClassBase::TypedDict => Type::SpecialForm(SpecialFormType::TypedDict),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,10 +276,7 @@ impl<'db> Mro<'db> {
|
||||
continue;
|
||||
}
|
||||
match base {
|
||||
ClassBase::Class(_)
|
||||
| ClassBase::Generic
|
||||
| ClassBase::Protocol
|
||||
| ClassBase::TypedDict => {
|
||||
ClassBase::Class(_) | ClassBase::Generic | ClassBase::Protocol => {
|
||||
errors.push(DuplicateBaseError {
|
||||
duplicate_base: base,
|
||||
first_index: *first_index,
|
||||
|
||||
@@ -146,7 +146,6 @@ fn check_class_declaration<'db>(
|
||||
|
||||
let mut subclass_overrides_superclass_declaration = false;
|
||||
let mut has_dynamic_superclass = false;
|
||||
let mut has_typeddict_in_mro = false;
|
||||
let mut liskov_diagnostic_emitted = false;
|
||||
let mut overridden_final_method = None;
|
||||
|
||||
@@ -157,10 +156,6 @@ fn check_class_declaration<'db>(
|
||||
has_dynamic_superclass = true;
|
||||
continue;
|
||||
}
|
||||
ClassBase::TypedDict => {
|
||||
has_typeddict_in_mro = true;
|
||||
continue;
|
||||
}
|
||||
ClassBase::Class(class) => class,
|
||||
};
|
||||
|
||||
@@ -262,6 +257,12 @@ fn check_class_declaration<'db>(
|
||||
continue;
|
||||
}
|
||||
|
||||
// TypedDict classes cannot have methods, so there's no point in checking for Liskov
|
||||
// violations. We already emit `invalid-typed-dict-statement` for this case.
|
||||
if literal.is_typed_dict(db) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(superclass_type_as_callable) = superclass_type.try_upcast_to_callable(db) else {
|
||||
continue;
|
||||
};
|
||||
@@ -286,7 +287,7 @@ fn check_class_declaration<'db>(
|
||||
}
|
||||
|
||||
if !subclass_overrides_superclass_declaration && !has_dynamic_superclass {
|
||||
if has_typeddict_in_mro {
|
||||
if literal.is_typed_dict(db) {
|
||||
if !KnownClass::TypedDictFallback
|
||||
.to_instance(db)
|
||||
.member(db, &member.name)
|
||||
|
||||
@@ -173,9 +173,6 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
||||
(ClassBase::Generic, _) => Ordering::Less,
|
||||
(_, ClassBase::Generic) => Ordering::Greater,
|
||||
|
||||
(ClassBase::TypedDict, _) => Ordering::Less,
|
||||
(_, ClassBase::TypedDict) => Ordering::Greater,
|
||||
|
||||
(ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => {
|
||||
dynamic_elements_ordering(left, right)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user