From 39f6151d81004cd3ce2ddd9e35a76d14ffdb08d9 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Mon, 31 Mar 2025 18:21:12 -0700 Subject: [PATCH 1/2] [red-knot] support Any as a class in typeshed --- crates/red_knot_python_semantic/src/types.rs | 1 + .../src/types/class.rs | 18 +++++++++++---- .../src/types/class_base.rs | 6 ++++- .../src/types/display.rs | 23 ++++++++++--------- .../src/types/infer.rs | 17 +++++++++++++- .../src/types/narrow.rs | 10 +++++++- .../vendor/typeshed/stdlib/typing.pyi | 2 +- 7 files changed, 57 insertions(+), 20 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index db8d74b6a40be..923884d26888f 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -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, [ diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs index 33e14acd24710..fb862e64a526d 100644 --- a/crates/red_knot_python_semantic/src/types/class.rs +++ b/crates/red_knot_python_semantic/src/types/class.rs @@ -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 diff --git a/crates/red_knot_python_semantic/src/types/class_base.rs b/crates/red_knot_python_semantic/src/types/class_base.rs index 2d4161dc33508..e7606468ae74c 100644 --- a/crates/red_knot_python_semantic/src/types/class_base.rs +++ b/crates/red_knot_python_semantic/src/types/class_base.rs @@ -61,7 +61,11 @@ impl<'db> ClassBase<'db> { pub(super) fn try_from_type(db: &'db dyn Db, ty: Type<'db>) -> Option { 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__`? diff --git a/crates/red_knot_python_semantic/src/types/display.rs b/crates/red_knot_python_semantic/src/types/display.rs index 3d4fb84c8c6be..0b31eb69e3dc8 100644 --- a/crates/red_knot_python_semantic/src/types/display.rs +++ b/crates/red_knot_python_semantic/src/types/display.rs @@ -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), } } } diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 4c1d078ea0ae2..476f0daa0f1df 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -6509,7 +6509,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() @@ -6589,6 +6596,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) } diff --git a/crates/red_knot_python_semantic/src/types/narrow.rs b/crates/red_knot_python_semantic/src/types/narrow.rs index ceaec3d3c1f00..9410253a79a77 100644 --- a/crates/red_knot_python_semantic/src/types/narrow.rs +++ b/crates/red_knot_python_semantic/src/types/narrow.rs @@ -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) } diff --git a/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi b/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi index 5875b6915762d..cf69c11bcaaa0 100644 --- a/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi +++ b/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi @@ -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: ... From 6a0c8b297b32714c6c4fbd043d11550c0ba079c9 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 1 Apr 2025 09:35:10 -0700 Subject: [PATCH 2/2] add comment on old instance Any representation --- crates/red_knot_python_semantic/src/types/class.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs index fb862e64a526d..c12316ccb497d 100644 --- a/crates/red_knot_python_semantic/src/types/class.rs +++ b/crates/red_knot_python_semantic/src/types/class.rs @@ -1511,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,