mirror of https://github.com/astral-sh/ruff
[red-knot] support Any as a class in typeshed (#17107)
## Summary In https://github.com/python/typeshed/pull/13520 the typeshed definition of `typing.Any` was changed from `Any = object()` to `class Any: ...`. Our automated typeshed updater pulled down this change in https://github.com/astral-sh/ruff/pull/17106, with the consequence that we no longer understand `Any`, which is... not good. This PR gives us the ability to understand `Any` defined as a class instead of `object()`. It doesn't remove our ability to understand the old form. Perhaps at some point we'll want to remove it, but for now we may as well support both old and new typeshed? This also directly patches typeshed to use the new form of `Any`; this is purely to work around our tests that no known class is inferred as `Unknown`, which otherwise fail with the old typeshed and the changes in this PR. (All other tests pass.) This patch to typeshed will shortly be subsumed by https://github.com/astral-sh/ruff/pull/17106 anyway. ## Test Plan Without the typeshed change in this PR, all tests pass except for the two `known_class_doesnt_fallback_to_unknown_unexpectedly_*` tests (so we still support the old form of defining `Any`). With the typeshed change in this PR, all tests pass, so we now support the new form in a way that is indistinguishable to our test suite from the old form. And indistinguishable to the ecosystem check: after rebasing https://github.com/astral-sh/ruff/pull/17106 on this PR, there's zero ecosystem impact.
This commit is contained in:
parent
5a876ed25e
commit
3f63c08728
|
|
@ -2950,6 +2950,7 @@ impl<'db> Type<'db> {
|
|||
// https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||
let ty = match class.known(db) {
|
||||
Some(KnownClass::Any) => Type::any(),
|
||||
Some(KnownClass::Complex) => UnionType::from_elements(
|
||||
db,
|
||||
[
|
||||
|
|
|
|||
|
|
@ -840,6 +840,7 @@ pub enum KnownClass {
|
|||
// Typeshed
|
||||
NoneType, // Part of `types` for Python >= 3.10
|
||||
// Typing
|
||||
Any,
|
||||
StdlibAlias,
|
||||
SpecialForm,
|
||||
TypeVar,
|
||||
|
|
@ -903,7 +904,8 @@ impl<'db> KnownClass {
|
|||
|
||||
Self::NoneType => Truthiness::AlwaysFalse,
|
||||
|
||||
Self::BaseException
|
||||
Self::Any
|
||||
| Self::BaseException
|
||||
| Self::Object
|
||||
| Self::OrderedDict
|
||||
| Self::BaseExceptionGroup
|
||||
|
|
@ -944,6 +946,7 @@ impl<'db> KnownClass {
|
|||
|
||||
pub(crate) fn name(self, db: &'db dyn Db) -> &'static str {
|
||||
match self {
|
||||
Self::Any => "Any",
|
||||
Self::Bool => "bool",
|
||||
Self::Object => "object",
|
||||
Self::Bytes => "bytes",
|
||||
|
|
@ -1150,7 +1153,8 @@ impl<'db> KnownClass {
|
|||
| Self::MethodWrapperType
|
||||
| Self::WrapperDescriptorType => KnownModule::Types,
|
||||
Self::NoneType => KnownModule::Typeshed,
|
||||
Self::SpecialForm
|
||||
Self::Any
|
||||
| Self::SpecialForm
|
||||
| Self::TypeVar
|
||||
| Self::StdlibAlias
|
||||
| Self::SupportsIndex
|
||||
|
|
@ -1201,7 +1205,8 @@ impl<'db> KnownClass {
|
|||
| Self::TypeAliasType
|
||||
| Self::NotImplementedType => true,
|
||||
|
||||
Self::Bool
|
||||
Self::Any
|
||||
| Self::Bool
|
||||
| Self::Object
|
||||
| Self::Bytes
|
||||
| Self::Type
|
||||
|
|
@ -1258,7 +1263,8 @@ impl<'db> KnownClass {
|
|||
| Self::TypeAliasType
|
||||
| Self::NotImplementedType => true,
|
||||
|
||||
Self::Bool
|
||||
Self::Any
|
||||
| Self::Bool
|
||||
| Self::Object
|
||||
| Self::Bytes
|
||||
| Self::Tuple
|
||||
|
|
@ -1311,6 +1317,7 @@ impl<'db> KnownClass {
|
|||
// We assert that this match is exhaustive over the right-hand side in the unit test
|
||||
// `known_class_roundtrip_from_str()`
|
||||
let candidate = match class_name {
|
||||
"Any" => Self::Any,
|
||||
"bool" => Self::Bool,
|
||||
"object" => Self::Object,
|
||||
"bytes" => Self::Bytes,
|
||||
|
|
@ -1377,7 +1384,8 @@ impl<'db> KnownClass {
|
|||
/// Return `true` if the module of `self` matches `module`
|
||||
fn check_module(self, db: &'db dyn Db, module: KnownModule) -> bool {
|
||||
match self {
|
||||
Self::Bool
|
||||
Self::Any
|
||||
| Self::Bool
|
||||
| Self::Object
|
||||
| Self::Bytes
|
||||
| Self::Type
|
||||
|
|
@ -1503,6 +1511,9 @@ pub enum KnownInstanceType<'db> {
|
|||
/// The symbol `typing.Never` available since 3.11 (which can also be found as `typing_extensions.Never`)
|
||||
Never,
|
||||
/// The symbol `typing.Any` (which can also be found as `typing_extensions.Any`)
|
||||
/// This is not used since typeshed switched to representing `Any` as a class; now we use
|
||||
/// `KnownClass::Any` instead. But we still support the old `Any = object()` representation, at
|
||||
/// least for now. TODO maybe remove?
|
||||
Any,
|
||||
/// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`)
|
||||
Tuple,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,11 @@ impl<'db> ClassBase<'db> {
|
|||
pub(super) fn try_from_type(db: &'db dyn Db, ty: Type<'db>) -> Option<Self> {
|
||||
match ty {
|
||||
Type::Dynamic(dynamic) => Some(Self::Dynamic(dynamic)),
|
||||
Type::ClassLiteral(literal) => Some(Self::Class(literal.class())),
|
||||
Type::ClassLiteral(literal) => Some(if literal.class().is_known(db, KnownClass::Any) {
|
||||
Self::Dynamic(DynamicType::Any)
|
||||
} else {
|
||||
Self::Class(literal.class())
|
||||
}),
|
||||
Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs?
|
||||
Type::Intersection(_) => None, // TODO -- probably incorrect?
|
||||
Type::Instance(_) => None, // TODO -- handle `__mro_entries__`?
|
||||
|
|
|
|||
|
|
@ -33,18 +33,19 @@ pub struct DisplayType<'db> {
|
|||
impl Display for DisplayType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let representation = self.ty.representation(self.db);
|
||||
if matches!(
|
||||
self.ty,
|
||||
match self.ty {
|
||||
Type::ClassLiteral(literal) if literal.class().is_known(self.db, KnownClass::Any) => {
|
||||
write!(f, "typing.Any")
|
||||
}
|
||||
Type::IntLiteral(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
| Type::StringLiteral(_)
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::ClassLiteral(_)
|
||||
| Type::FunctionLiteral(_)
|
||||
) {
|
||||
write!(f, "Literal[{representation}]")
|
||||
} else {
|
||||
representation.fmt(f)
|
||||
| Type::BooleanLiteral(_)
|
||||
| Type::StringLiteral(_)
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::ClassLiteral(_)
|
||||
| Type::FunctionLiteral(_) => {
|
||||
write!(f, "Literal[{representation}]")
|
||||
}
|
||||
_ => representation.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6522,7 +6522,14 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
let name_ty = self.infer_expression(slice);
|
||||
match name_ty {
|
||||
Type::ClassLiteral(class_literal_ty) => {
|
||||
SubclassOfType::from(self.db(), class_literal_ty.class())
|
||||
if class_literal_ty
|
||||
.class()
|
||||
.is_known(self.db(), KnownClass::Any)
|
||||
{
|
||||
SubclassOfType::subclass_of_any()
|
||||
} else {
|
||||
SubclassOfType::from(self.db(), class_literal_ty.class())
|
||||
}
|
||||
}
|
||||
Type::KnownInstance(KnownInstanceType::Any) => {
|
||||
SubclassOfType::subclass_of_any()
|
||||
|
|
@ -6602,6 +6609,14 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
} = subscript;
|
||||
|
||||
match value_ty {
|
||||
Type::ClassLiteral(literal) if literal.class().is_known(self.db(), KnownClass::Any) => {
|
||||
self.context.report_lint(
|
||||
&INVALID_TYPE_FORM,
|
||||
subscript,
|
||||
format_args!("Type `typing.Any` expected no type parameter",),
|
||||
);
|
||||
Type::unknown()
|
||||
}
|
||||
Type::KnownInstance(known_instance) => {
|
||||
self.infer_parameterized_known_instance_type_expression(subscript, known_instance)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,15 @@ impl KnownConstraintFunction {
|
|||
}
|
||||
Some(builder.build())
|
||||
}
|
||||
Type::ClassLiteral(class_literal) => Some(constraint_fn(class_literal.class())),
|
||||
Type::ClassLiteral(class_literal) => {
|
||||
// At runtime (on Python 3.11+), this will return `True` for classes that actually
|
||||
// do inherit `typing.Any` and `False` otherwise. We could accurately model that?
|
||||
if class_literal.class().is_known(db, KnownClass::Any) {
|
||||
None
|
||||
} else {
|
||||
Some(constraint_fn(class_literal.class()))
|
||||
}
|
||||
}
|
||||
Type::SubclassOf(subclass_of_ty) => {
|
||||
subclass_of_ty.subclass_of().into_class().map(constraint_fn)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ if sys.version_info >= (3, 12):
|
|||
if sys.version_info >= (3, 13):
|
||||
__all__ += ["get_protocol_members", "is_protocol", "NoDefault", "TypeIs", "ReadOnly"]
|
||||
|
||||
Any = object()
|
||||
class Any: ...
|
||||
|
||||
class _Final: ...
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue