[ty] Remove special casing for string-literal-in-tuple __contains__ (#19642)

This commit is contained in:
Alex Waygood 2025-07-31 11:28:03 +01:00 committed by GitHub
parent 32c454bb56
commit 27b03a9d7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 156 additions and 243 deletions

View file

@ -78,7 +78,7 @@ use crate::types::visitor::any_over_type;
use crate::types::{
BoundMethodType, CallableType, ClassLiteral, ClassType, DeprecatedInstance, DynamicType,
KnownClass, Truthiness, Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance,
UnionBuilder, walk_type_mapping,
UnionBuilder, all_members, walk_type_mapping,
};
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
@ -1082,6 +1082,8 @@ pub enum KnownFunction {
EnumMembers,
/// `ty_extensions.all_members`
AllMembers,
/// `ty_extensions.has_member`
HasMember,
/// `ty_extensions.top_materialization`
TopMaterialization,
/// `ty_extensions.bottom_materialization`
@ -1150,6 +1152,7 @@ impl KnownFunction {
| Self::DunderAllNames
| Self::EnumMembers
| Self::StaticAssert
| Self::HasMember
| Self::AllMembers => module.is_ty_extensions(),
Self::ImportModule => module.is_importlib(),
}
@ -1186,6 +1189,16 @@ impl KnownFunction {
}
}
KnownFunction::HasMember => {
let [Some(ty), Some(Type::StringLiteral(member))] = parameter_types else {
return;
};
let ty_members = all_members(db, *ty);
overload.set_return_type(Type::BooleanLiteral(
ty_members.iter().any(|m| m.name == member.value(db)),
));
}
KnownFunction::AssertType => {
let [Some(actual_ty), Some(asserted_ty)] = parameter_types else {
return;
@ -1444,6 +1457,7 @@ pub(crate) mod tests {
| KnownFunction::IsEquivalentTo
| KnownFunction::TopMaterialization
| KnownFunction::BottomMaterialization
| KnownFunction::HasMember
| KnownFunction::AllMembers => KnownModule::TyExtensions,
KnownFunction::ImportModule => KnownModule::ImportLib,

View file

@ -7691,38 +7691,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
right: Type<'db>,
range: TextRange,
) -> Result<Type<'db>, CompareUnsupportedError<'db>> {
let is_str_literal_in_tuple = |literal: Type<'db>, tuple: TupleType<'db>| {
// Protect against doing a lot of work for pathologically large
// tuples.
//
// Ref: https://github.com/astral-sh/ruff/pull/18251#discussion_r2115909311
let (minimum_length, _) = tuple.tuple(self.db()).len().size_hint();
if minimum_length > 1 << 12 {
return None;
}
let mut definitely_true = false;
let mut definitely_false = true;
for element in tuple.tuple(self.db()).all_elements().copied() {
if element.is_string_literal() {
if literal == element {
definitely_true = true;
definitely_false = false;
}
} else if !literal.is_disjoint_from(self.db(), element) {
definitely_false = false;
}
}
if definitely_true {
Some(true)
} else if definitely_false {
Some(false)
} else {
None
}
};
// Note: identity (is, is not) for equal builtin types is unreliable and not part of the
// language spec.
// - `[ast::CompOp::Is]`: return `false` if unequal, `bool` if equal
@ -7854,30 +7822,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
}
(Type::StringLiteral(_), Type::Tuple(tuple)) if op == ast::CmpOp::In => {
if let Some(answer) = is_str_literal_in_tuple(left, tuple) {
return Ok(Type::BooleanLiteral(answer));
}
self.infer_binary_type_comparison(
KnownClass::Str.to_instance(self.db()),
op,
right,
range,
)
}
(Type::StringLiteral(_), Type::Tuple(tuple)) if op == ast::CmpOp::NotIn => {
if let Some(answer) = is_str_literal_in_tuple(left, tuple) {
return Ok(Type::BooleanLiteral(!answer));
}
self.infer_binary_type_comparison(
KnownClass::Str.to_instance(self.db()),
op,
right,
range,
)
}
(Type::StringLiteral(_), _) => self.infer_binary_type_comparison(
KnownClass::Str.to_instance(self.db()),
op,