mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-20 12:35:40 +00:00
[ty] Fix bug where ty would think all types had an __mro__ attribute (#20995)
This commit is contained in:
parent
3c7f56f582
commit
db0e921db1
32 changed files with 780 additions and 599 deletions
|
|
@ -4364,14 +4364,6 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
Type::ClassLiteral(..) | Type::GenericAlias(..) | Type::SubclassOf(..) => {
|
||||
let class_attr_plain = self.find_name_in_mro_with_policy(db, name_str,policy).expect(
|
||||
"Calling `find_name_in_mro` on class literals and subclass-of types should always return `Some`",
|
||||
);
|
||||
|
||||
if name == "__mro__" {
|
||||
return class_attr_plain;
|
||||
}
|
||||
|
||||
if let Some(enum_class) = match self {
|
||||
Type::ClassLiteral(literal) => Some(literal),
|
||||
Type::SubclassOf(subclass_of) => subclass_of
|
||||
|
|
@ -4392,6 +4384,10 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
let class_attr_plain = self.find_name_in_mro_with_policy(db, name_str,policy).expect(
|
||||
"Calling `find_name_in_mro` on class literals and subclass-of types should always return `Some`",
|
||||
);
|
||||
|
||||
let class_attr_fallback = Self::try_call_dunder_get_on_attribute(
|
||||
db,
|
||||
class_attr_plain,
|
||||
|
|
|
|||
|
|
@ -1981,11 +1981,6 @@ impl<'db> ClassLiteral<'db> {
|
|||
name: &str,
|
||||
policy: MemberLookupPolicy,
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
if name == "__mro__" {
|
||||
let tuple_elements = self.iter_mro(db, specialization);
|
||||
return Place::bound(Type::heterogeneous_tuple(db, tuple_elements)).into();
|
||||
}
|
||||
|
||||
self.class_member_from_mro(db, name, policy, self.iter_mro(db, specialization))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -348,6 +348,27 @@ impl<'db> ClassBase<'db> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn display(self, db: &'db dyn Db) -> impl std::fmt::Display {
|
||||
struct ClassBaseDisplay<'db> {
|
||||
db: &'db dyn Db,
|
||||
base: ClassBase<'db>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ClassBaseDisplay<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.base {
|
||||
ClassBase::Dynamic(dynamic) => dynamic.fmt(f),
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClassBaseDisplay { db, base: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<ClassType<'db>> for ClassBase<'db> {
|
||||
|
|
|
|||
|
|
@ -1319,6 +1319,8 @@ pub enum KnownFunction {
|
|||
HasMember,
|
||||
/// `ty_extensions.reveal_protocol_interface`
|
||||
RevealProtocolInterface,
|
||||
/// `ty_extensions.reveal_mro`
|
||||
RevealMro,
|
||||
/// `ty_extensions.range_constraint`
|
||||
RangeConstraint,
|
||||
/// `ty_extensions.negated_range_constraint`
|
||||
|
|
@ -1397,6 +1399,7 @@ impl KnownFunction {
|
|||
| Self::RevealProtocolInterface
|
||||
| Self::RangeConstraint
|
||||
| Self::NegatedRangeConstraint
|
||||
| Self::RevealMro
|
||||
| Self::AllMembers => module.is_ty_extensions(),
|
||||
Self::ImportModule => module.is_importlib(),
|
||||
|
||||
|
|
@ -1619,6 +1622,85 @@ impl KnownFunction {
|
|||
}
|
||||
}
|
||||
|
||||
KnownFunction::RevealMro => {
|
||||
let [Some(param_type)] = parameter_types else {
|
||||
return;
|
||||
};
|
||||
let mut good_argument = true;
|
||||
let classes = match param_type {
|
||||
Type::ClassLiteral(class) => vec![ClassType::NonGeneric(*class)],
|
||||
Type::GenericAlias(generic_alias) => vec![ClassType::Generic(*generic_alias)],
|
||||
Type::Union(union) => {
|
||||
let elements = union.elements(db);
|
||||
let mut classes = Vec::with_capacity(elements.len());
|
||||
for element in elements {
|
||||
match element {
|
||||
Type::ClassLiteral(class) => {
|
||||
classes.push(ClassType::NonGeneric(*class));
|
||||
}
|
||||
Type::GenericAlias(generic_alias) => {
|
||||
classes.push(ClassType::Generic(*generic_alias));
|
||||
}
|
||||
_ => {
|
||||
good_argument = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
classes
|
||||
}
|
||||
_ => {
|
||||
good_argument = false;
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
if !good_argument {
|
||||
let Some(builder) =
|
||||
context.report_lint(&INVALID_ARGUMENT_TYPE, call_expression)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let mut diagnostic =
|
||||
builder.into_diagnostic("Invalid argument to `reveal_mro`");
|
||||
diagnostic.set_primary_message(format_args!(
|
||||
"Can only pass a class object, generic alias or a union thereof"
|
||||
));
|
||||
return;
|
||||
}
|
||||
if let Some(builder) =
|
||||
context.report_diagnostic(DiagnosticId::RevealedType, Severity::Info)
|
||||
{
|
||||
let mut diag = builder.into_diagnostic("Revealed MRO");
|
||||
let span = context.span(&call_expression.arguments.args[0]);
|
||||
let mut message = String::new();
|
||||
for (i, class) in classes.iter().enumerate() {
|
||||
message.push('(');
|
||||
for class in class.iter_mro(db) {
|
||||
message.push_str(&class.display(db).to_string());
|
||||
// Omit the comma for the last element (which is always `object`)
|
||||
if class
|
||||
.into_class()
|
||||
.is_none_or(|base| !base.is_object(context.db()))
|
||||
{
|
||||
message.push_str(", ");
|
||||
}
|
||||
}
|
||||
// If the last element was also the first element
|
||||
// (i.e., it's a length-1 tuple -- which can only happen if we're revealing
|
||||
// the MRO for `object` itself), add a trailing comma so that it's still a
|
||||
// valid tuple display.
|
||||
if class.is_object(db) {
|
||||
message.push(',');
|
||||
}
|
||||
message.push(')');
|
||||
if i < classes.len() - 1 {
|
||||
message.push_str(" | ");
|
||||
}
|
||||
}
|
||||
diag.annotate(Annotation::primary(span).message(message));
|
||||
}
|
||||
}
|
||||
|
||||
KnownFunction::IsInstance | KnownFunction::IsSubclass => {
|
||||
let [Some(first_arg), Some(second_argument)] = parameter_types else {
|
||||
return;
|
||||
|
|
@ -1822,6 +1904,7 @@ pub(crate) mod tests {
|
|||
| KnownFunction::RevealProtocolInterface
|
||||
| KnownFunction::RangeConstraint
|
||||
| KnownFunction::NegatedRangeConstraint
|
||||
| KnownFunction::RevealMro
|
||||
| KnownFunction::AllMembers => KnownModule::TyExtensions,
|
||||
|
||||
KnownFunction::ImportModule => KnownModule::ImportLib,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue