mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:34:57 +00:00
[ty] Add precise inference for indexing, slicing and unpacking NamedTuple
instances (#19560)
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
This commit is contained in:
parent
11d2cb6d56
commit
9f6146a13d
7 changed files with 102 additions and 36 deletions
|
@ -9389,7 +9389,19 @@ impl<'db> BoundSuperType<'db> {
|
|||
));
|
||||
}
|
||||
|
||||
let pivot_class = ClassBase::try_from_type(db, pivot_class_type).ok_or({
|
||||
// TODO: having to get a class-literal just to pass it in here is silly.
|
||||
// `BoundSuperType` should probably not be using `ClassBase::try_from_type` here;
|
||||
// this also leads to false negatives in some cases. See discussion in
|
||||
// <https://github.com/astral-sh/ruff/pull/19560#discussion_r2271570071>.
|
||||
let pivot_class = ClassBase::try_from_type(
|
||||
db,
|
||||
pivot_class_type,
|
||||
KnownClass::Object
|
||||
.to_class_literal(db)
|
||||
.into_class_literal()
|
||||
.expect("`object` should always exist in typeshed"),
|
||||
)
|
||||
.ok_or({
|
||||
BoundSuperError::InvalidPivotClassType {
|
||||
pivot_class: pivot_class_type,
|
||||
}
|
||||
|
|
|
@ -2240,7 +2240,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
/// y: str = "a"
|
||||
/// ```
|
||||
/// we return a map `{"x": (int, None), "y": (str, Some(Literal["a"]))}`.
|
||||
fn own_fields(
|
||||
pub(super) fn own_fields(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use crate::Db;
|
||||
use crate::types::generics::Specialization;
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{
|
||||
ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, SpecialFormType,
|
||||
Type, TypeMapping, TypeTransformer, todo_type,
|
||||
ClassLiteral, ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator,
|
||||
SpecialFormType, Type, TypeMapping, TypeTransformer, todo_type,
|
||||
};
|
||||
|
||||
/// Enumeration of the possible kinds of types we allow in class bases.
|
||||
|
@ -68,15 +69,28 @@ impl<'db> ClassBase<'db> {
|
|||
/// Attempt to resolve `ty` into a `ClassBase`.
|
||||
///
|
||||
/// Return `None` if `ty` is not an acceptable type for a class base.
|
||||
pub(super) fn try_from_type(db: &'db dyn Db, ty: Type<'db>) -> Option<Self> {
|
||||
pub(super) fn try_from_type(
|
||||
db: &'db dyn Db,
|
||||
ty: Type<'db>,
|
||||
subclass: ClassLiteral<'db>,
|
||||
) -> Option<Self> {
|
||||
match ty {
|
||||
Type::Dynamic(dynamic) => Some(Self::Dynamic(dynamic)),
|
||||
Type::ClassLiteral(literal) => {
|
||||
if literal.is_known(db, KnownClass::Any) {
|
||||
Some(Self::Dynamic(DynamicType::Any))
|
||||
} else if literal.is_known(db, KnownClass::NamedTuple) {
|
||||
// TODO: Figure out the tuple spec for the named tuple
|
||||
Self::try_from_type(db, KnownClass::Tuple.to_class_literal(db))
|
||||
let fields = subclass.own_fields(db, None);
|
||||
Self::try_from_type(
|
||||
db,
|
||||
TupleType::heterogeneous(
|
||||
db,
|
||||
fields.values().map(|field| field.declared_ty),
|
||||
)?
|
||||
.to_class_type(db)
|
||||
.into(),
|
||||
subclass,
|
||||
)
|
||||
} else {
|
||||
Some(Self::Class(literal.default_specialization(db)))
|
||||
}
|
||||
|
@ -85,7 +99,7 @@ impl<'db> ClassBase<'db> {
|
|||
Type::NominalInstance(instance)
|
||||
if instance.class(db).is_known(db, KnownClass::GenericAlias) =>
|
||||
{
|
||||
Self::try_from_type(db, todo_type!("GenericAlias instance"))
|
||||
Self::try_from_type(db, todo_type!("GenericAlias instance"), subclass)
|
||||
}
|
||||
Type::SubclassOf(subclass_of) => subclass_of
|
||||
.subclass_of()
|
||||
|
@ -95,7 +109,7 @@ impl<'db> ClassBase<'db> {
|
|||
let valid_element = inter
|
||||
.positive(db)
|
||||
.iter()
|
||||
.find_map(|elem| ClassBase::try_from_type(db, *elem))?;
|
||||
.find_map(|elem| ClassBase::try_from_type(db, *elem, subclass))?;
|
||||
|
||||
if ty.is_disjoint_from(db, KnownClass::Type.to_instance(db)) {
|
||||
None
|
||||
|
@ -122,7 +136,7 @@ impl<'db> ClassBase<'db> {
|
|||
if union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.all(|elem| ClassBase::try_from_type(db, *elem).is_some())
|
||||
.all(|elem| ClassBase::try_from_type(db, *elem, subclass).is_some())
|
||||
{
|
||||
Some(ClassBase::Dynamic(*dynamic))
|
||||
} else {
|
||||
|
@ -135,7 +149,7 @@ impl<'db> ClassBase<'db> {
|
|||
// in which case we want to treat `Never` in a forgiving way and silence diagnostics
|
||||
Type::Never => Some(ClassBase::unknown()),
|
||||
|
||||
Type::TypeAlias(alias) => Self::try_from_type(db, alias.value_type(db)),
|
||||
Type::TypeAlias(alias) => Self::try_from_type(db, alias.value_type(db), subclass),
|
||||
|
||||
Type::PropertyInstance(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
|
@ -202,42 +216,44 @@ impl<'db> ClassBase<'db> {
|
|||
|
||||
// TODO: Classes inheriting from `typing.Type` et al. also have `Generic` in their MRO
|
||||
SpecialFormType::Dict => {
|
||||
Self::try_from_type(db, KnownClass::Dict.to_class_literal(db))
|
||||
Self::try_from_type(db, KnownClass::Dict.to_class_literal(db), subclass)
|
||||
}
|
||||
SpecialFormType::List => {
|
||||
Self::try_from_type(db, KnownClass::List.to_class_literal(db))
|
||||
Self::try_from_type(db, KnownClass::List.to_class_literal(db), subclass)
|
||||
}
|
||||
SpecialFormType::Type => {
|
||||
Self::try_from_type(db, KnownClass::Type.to_class_literal(db))
|
||||
Self::try_from_type(db, KnownClass::Type.to_class_literal(db), subclass)
|
||||
}
|
||||
SpecialFormType::Tuple => {
|
||||
Self::try_from_type(db, KnownClass::Tuple.to_class_literal(db))
|
||||
Self::try_from_type(db, KnownClass::Tuple.to_class_literal(db), subclass)
|
||||
}
|
||||
SpecialFormType::Set => {
|
||||
Self::try_from_type(db, KnownClass::Set.to_class_literal(db))
|
||||
Self::try_from_type(db, KnownClass::Set.to_class_literal(db), subclass)
|
||||
}
|
||||
SpecialFormType::FrozenSet => {
|
||||
Self::try_from_type(db, KnownClass::FrozenSet.to_class_literal(db))
|
||||
Self::try_from_type(db, KnownClass::FrozenSet.to_class_literal(db), subclass)
|
||||
}
|
||||
SpecialFormType::ChainMap => {
|
||||
Self::try_from_type(db, KnownClass::ChainMap.to_class_literal(db))
|
||||
Self::try_from_type(db, KnownClass::ChainMap.to_class_literal(db), subclass)
|
||||
}
|
||||
SpecialFormType::Counter => {
|
||||
Self::try_from_type(db, KnownClass::Counter.to_class_literal(db))
|
||||
Self::try_from_type(db, KnownClass::Counter.to_class_literal(db), subclass)
|
||||
}
|
||||
SpecialFormType::DefaultDict => {
|
||||
Self::try_from_type(db, KnownClass::DefaultDict.to_class_literal(db))
|
||||
Self::try_from_type(db, KnownClass::DefaultDict.to_class_literal(db), subclass)
|
||||
}
|
||||
SpecialFormType::Deque => {
|
||||
Self::try_from_type(db, KnownClass::Deque.to_class_literal(db))
|
||||
Self::try_from_type(db, KnownClass::Deque.to_class_literal(db), subclass)
|
||||
}
|
||||
SpecialFormType::OrderedDict => {
|
||||
Self::try_from_type(db, KnownClass::OrderedDict.to_class_literal(db))
|
||||
Self::try_from_type(db, KnownClass::OrderedDict.to_class_literal(db), subclass)
|
||||
}
|
||||
SpecialFormType::TypedDict => Some(Self::TypedDict),
|
||||
SpecialFormType::Callable => {
|
||||
Self::try_from_type(db, todo_type!("Support for Callable as a base class"))
|
||||
}
|
||||
SpecialFormType::Callable => Self::try_from_type(
|
||||
db,
|
||||
todo_type!("Support for Callable as a base class"),
|
||||
subclass,
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,7 +151,7 @@ impl<'db> Mro<'db> {
|
|||
)
|
||||
) =>
|
||||
{
|
||||
ClassBase::try_from_type(db, *single_base).map_or_else(
|
||||
ClassBase::try_from_type(db, *single_base, class.class_literal(db).0).map_or_else(
|
||||
|| Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)]))),
|
||||
|single_base| {
|
||||
if single_base.has_cyclic_mro(db) {
|
||||
|
@ -186,7 +186,7 @@ impl<'db> Mro<'db> {
|
|||
&original_bases[i + 1..],
|
||||
);
|
||||
} else {
|
||||
match ClassBase::try_from_type(db, *base) {
|
||||
match ClassBase::try_from_type(db, *base, class.class_literal(db).0) {
|
||||
Some(valid_base) => resolved_bases.push(valid_base),
|
||||
None => invalid_bases.push((i, *base)),
|
||||
}
|
||||
|
@ -253,7 +253,9 @@ impl<'db> Mro<'db> {
|
|||
// `inconsistent-mro` diagnostic (which would be accurate -- but not nearly as
|
||||
// precise!).
|
||||
for (index, base) in original_bases.iter().enumerate() {
|
||||
let Some(base) = ClassBase::try_from_type(db, *base) else {
|
||||
let Some(base) =
|
||||
ClassBase::try_from_type(db, *base, class.class_literal(db).0)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
base_to_indices.entry(base).or_default().push(index);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue