From 59e96707754c45abfcb3e6f06a9fbadacbf0eba4 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Mon, 2 Dec 2024 14:45:51 -0500 Subject: [PATCH 01/13] [red-knot] Add `typing.Any` as a spelling for the Any type We already had a representation for the Any type, which we would use e.g. for expressions without type annotations. We now recognize `typing.Any` as a way to refer to this type explicitly. Like other special forms, this is tracked correctly through aliasing, and isn't confused with local definitions that happen to have the same name. --- .../resources/mdtest/annotations/any.md | 50 +++++++++++++++++++ crates/red_knot_python_semantic/src/types.rs | 8 +++ .../src/types/infer.rs | 1 + .../red_knot_python_semantic/src/types/mro.rs | 1 + .../vendor/typeshed/stdlib/typing.pyi | 3 +- 5 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 crates/red_knot_python_semantic/resources/mdtest/annotations/any.md diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md new file mode 100644 index 0000000000000..540dc7c0d5097 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md @@ -0,0 +1,50 @@ +# Any + +## Annotation + +`typing.Any` is a way to name the Any type. + +```py +from typing import Any + +x: Any = 1 +x = "foo" + +def f(): + reveal_type(x) # revealed: Any +``` + +## Aliased to a different name + +If you alias `typing.Any` to another name, we still recognize that as a spelling +of the Any type. + +```py +from typing import Any as RenamedAny + +x: RenamedAny = 1 +x = "foo" + +def f(): + reveal_type(x) # revealed: Any +``` + +## Shadowed class + +If you define your own class named `Any`, using that in a type expression refers +to your class, and isn't a spelling of the Any type. + +> Note that the real name of the class shouldn't be `Any`, so that we can +> distinguish it from the Any type in the assertion below. + +```py +class LocalAny: + pass + +Any = LocalAny + +x: Any = Any() + +def f(): + reveal_type(x) # revealed: LocalAny +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 695a779a849de..0e2c939b2924a 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1556,6 +1556,7 @@ impl<'db> Type<'db> { Type::KnownInstance(KnownInstanceType::Never | KnownInstanceType::NoReturn) => { Type::Never } + Type::KnownInstance(KnownInstanceType::Any) => Type::Any, _ => todo_type!(), } } @@ -1896,6 +1897,8 @@ pub enum KnownInstanceType<'db> { NoReturn, /// 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`) + Any, /// A single instance of `typing.TypeVar` TypeVar(TypeVarInstance<'db>), /// A single instance of `typing.TypeAliasType` (PEP 695 type alias) @@ -1912,6 +1915,7 @@ impl<'db> KnownInstanceType<'db> { Self::TypeVar(_) => "TypeVar", Self::NoReturn => "NoReturn", Self::Never => "Never", + Self::Any => "Any", Self::TypeAliasType(_) => "TypeAliasType", } } @@ -1925,6 +1929,7 @@ impl<'db> KnownInstanceType<'db> { | Self::Union | Self::NoReturn | Self::Never + | Self::Any | Self::TypeAliasType(_) => Truthiness::AlwaysTrue, } } @@ -1937,6 +1942,7 @@ impl<'db> KnownInstanceType<'db> { Self::Union => "typing.Union", Self::NoReturn => "typing.NoReturn", Self::Never => "typing.Never", + Self::Any => "typing.Any", Self::TypeVar(typevar) => typevar.name(db), Self::TypeAliasType(_) => "typing.TypeAliasType", } @@ -1950,6 +1956,7 @@ impl<'db> KnownInstanceType<'db> { Self::Union => KnownClass::SpecialForm, Self::NoReturn => KnownClass::SpecialForm, Self::Never => KnownClass::SpecialForm, + Self::Any => KnownClass::SpecialForm, Self::TypeVar(_) => KnownClass::TypeVar, Self::TypeAliasType(_) => KnownClass::TypeAliasType, } @@ -1969,6 +1976,7 @@ impl<'db> KnownInstanceType<'db> { return None; } match (module.name().as_str(), instance_name) { + ("typing" | "typing_extensions", "Any") => Some(Self::Any), ("typing" | "typing_extensions", "Literal") => Some(Self::Literal), ("typing" | "typing_extensions", "Optional") => Some(Self::Optional), ("typing" | "typing_extensions", "Union") => Some(Self::Union), diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 7224ac0c9daba..169dbf5a9ef4d 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -4642,6 +4642,7 @@ impl<'db> TypeInferenceBuilder<'db> { ); Type::Unknown } + KnownInstanceType::Any => Type::Any, } } diff --git a/crates/red_knot_python_semantic/src/types/mro.rs b/crates/red_knot_python_semantic/src/types/mro.rs index e576d7c634647..a21e7c5861975 100644 --- a/crates/red_knot_python_semantic/src/types/mro.rs +++ b/crates/red_knot_python_semantic/src/types/mro.rs @@ -377,6 +377,7 @@ impl<'db> ClassBase<'db> { | KnownInstanceType::Union | KnownInstanceType::NoReturn | KnownInstanceType::Never + | KnownInstanceType::Any | KnownInstanceType::Optional => None, }, } diff --git a/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi b/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi index 8f0d4fbb6a024..10792f9cd92d0 100644 --- a/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi +++ b/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi @@ -131,8 +131,6 @@ if sys.version_info >= (3, 12): if sys.version_info >= (3, 13): __all__ += ["get_protocol_members", "is_protocol", "NoDefault", "TypeIs", "ReadOnly"] -Any = object() - class _Final: ... def final(f: _T) -> _T: ... @@ -199,6 +197,7 @@ class _SpecialForm(_Final): def __or__(self, other: Any) -> _SpecialForm: ... def __ror__(self, other: Any) -> _SpecialForm: ... +Any: _SpecialForm Union: _SpecialForm Generic: _SpecialForm # Protocol is only present in 3.8 and later, but mypy needs it unconditionally From bb2e7d5785807b878848214bf5cb4a3f41003c43 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Mon, 2 Dec 2024 15:01:27 -0500 Subject: [PATCH 02/13] Reflow commentary in Any mdtest --- .../resources/mdtest/annotations/any.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md index 540dc7c0d5097..26762a7f7331a 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md @@ -16,8 +16,7 @@ def f(): ## Aliased to a different name -If you alias `typing.Any` to another name, we still recognize that as a spelling -of the Any type. +If you alias `typing.Any` to another name, we still recognize that as a spelling of the Any type. ```py from typing import Any as RenamedAny @@ -31,11 +30,11 @@ def f(): ## Shadowed class -If you define your own class named `Any`, using that in a type expression refers -to your class, and isn't a spelling of the Any type. +If you define your own class named `Any`, using that in a type expression refers to your class, and +isn't a spelling of the Any type. -> Note that the real name of the class shouldn't be `Any`, so that we can -> distinguish it from the Any type in the assertion below. +> Note that the real name of the class shouldn't be `Any`, so that we can distinguish it from the +> Any type in the assertion below. ```py class LocalAny: From 0bde1ad6af4a2a4d4ed8d11a93e9b5b6d1e0cab7 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Mon, 2 Dec 2024 15:56:57 -0500 Subject: [PATCH 03/13] Revert modified vendored typeshed This goes back to the actual definition of `typing.Any`. --- crates/red_knot_python_semantic/src/types.rs | 6 ++++-- crates/red_knot_python_semantic/src/types/infer.rs | 12 ++++++++++++ .../vendor/typeshed/stdlib/typing.pyi | 3 ++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 0e2c939b2924a..6f343419ca037 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1956,7 +1956,8 @@ impl<'db> KnownInstanceType<'db> { Self::Union => KnownClass::SpecialForm, Self::NoReturn => KnownClass::SpecialForm, Self::Never => KnownClass::SpecialForm, - Self::Any => KnownClass::SpecialForm, + // typing.Any is _not_ currently defined as a _SpecialForm in the typeshed. + Self::Any => KnownClass::Object, Self::TypeVar(_) => KnownClass::TypeVar, Self::TypeAliasType(_) => KnownClass::TypeAliasType, } @@ -1976,7 +1977,8 @@ impl<'db> KnownInstanceType<'db> { return None; } match (module.name().as_str(), instance_name) { - ("typing" | "typing_extensions", "Any") => Some(Self::Any), + // NOTE: This method is currently only called for typing _SpecialForms. typing.Any is + // not currently defined as a special form, so detecting it is handled elsewhere. ("typing" | "typing_extensions", "Literal") => Some(Self::Literal), ("typing" | "typing_extensions", "Optional") => Some(Self::Optional), ("typing" | "typing_extensions", "Union") => Some(Self::Union), diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 169dbf5a9ef4d..9b19d43d8a196 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -1655,6 +1655,18 @@ impl<'db> TypeInferenceBuilder<'db> { let value = assignment.value(); let name = assignment.name(); + // NOTE: typing.Any is _not_ currently defined as a _SpecialForm in the typeshed, so we + // can't detect it in the same was a typing.NoReturn and friends. If the definition in the + // typeshed changes, this heuristic will need to change as well. + if let Some(vendored) = definition.file(self.db).path(self.db).as_vendored_path() { + if name.id == "Any" && vendored.as_str() == "stdlib/typing.pyi" { + let target_ty = Type::KnownInstance(KnownInstanceType::Any); + self.store_expression_type(name, target_ty); + self.add_binding(name.into(), definition, target_ty); + return; + } + } + self.infer_standalone_expression(value); let value_ty = self.expression_ty(value); diff --git a/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi b/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi index 10792f9cd92d0..8f0d4fbb6a024 100644 --- a/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi +++ b/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi @@ -131,6 +131,8 @@ if sys.version_info >= (3, 12): if sys.version_info >= (3, 13): __all__ += ["get_protocol_members", "is_protocol", "NoDefault", "TypeIs", "ReadOnly"] +Any = object() + class _Final: ... def final(f: _T) -> _T: ... @@ -197,7 +199,6 @@ class _SpecialForm(_Final): def __or__(self, other: Any) -> _SpecialForm: ... def __ror__(self, other: Any) -> _SpecialForm: ... -Any: _SpecialForm Union: _SpecialForm Generic: _SpecialForm # Protocol is only present in 3.8 and later, but mypy needs it unconditionally From 0f56794a231b7597ed3133c90462c6489bcf93c2 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Mon, 2 Dec 2024 16:00:25 -0500 Subject: [PATCH 04/13] Verify correct Any via an assignable check --- .../resources/mdtest/annotations/any.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md index 26762a7f7331a..e637bb9a395ec 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md @@ -33,17 +33,16 @@ def f(): If you define your own class named `Any`, using that in a type expression refers to your class, and isn't a spelling of the Any type. -> Note that the real name of the class shouldn't be `Any`, so that we can distinguish it from the -> Any type in the assertion below. - ```py -class LocalAny: +class Any: pass -Any = LocalAny - -x: Any = Any() +x: Any def f(): - reveal_type(x) # revealed: LocalAny + reveal_type(x) # revealed: Any + +# This verifies that we're not accidentally seeing typing.Any, since str is assignable +# to that but not to our locally defined class. +y: Any = "not an Any" # error: [invalid-assignment] ``` From f36e533e50ae29a0cfda54e216a8ad336437e974 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Mon, 2 Dec 2024 16:07:56 -0500 Subject: [PATCH 05/13] Make Any detection slightly more like other detection --- crates/red_knot_python_semantic/src/types.rs | 3 +-- .../src/types/infer.rs | 24 +++++++++---------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 6f343419ca037..4c28a0f78c4ac 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1977,8 +1977,7 @@ impl<'db> KnownInstanceType<'db> { return None; } match (module.name().as_str(), instance_name) { - // NOTE: This method is currently only called for typing _SpecialForms. typing.Any is - // not currently defined as a special form, so detecting it is handled elsewhere. + ("typing" | "typing_extensions", "Any") => Some(Self::Any), ("typing" | "typing_extensions", "Literal") => Some(Self::Literal), ("typing" | "typing_extensions", "Optional") => Some(Self::Optional), ("typing" | "typing_extensions", "Union") => Some(Self::Union), diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 9b19d43d8a196..e87947a8c50ff 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -1655,24 +1655,12 @@ impl<'db> TypeInferenceBuilder<'db> { let value = assignment.value(); let name = assignment.name(); - // NOTE: typing.Any is _not_ currently defined as a _SpecialForm in the typeshed, so we - // can't detect it in the same was a typing.NoReturn and friends. If the definition in the - // typeshed changes, this heuristic will need to change as well. - if let Some(vendored) = definition.file(self.db).path(self.db).as_vendored_path() { - if name.id == "Any" && vendored.as_str() == "stdlib/typing.pyi" { - let target_ty = Type::KnownInstance(KnownInstanceType::Any); - self.store_expression_type(name, target_ty); - self.add_binding(name.into(), definition, target_ty); - return; - } - } - self.infer_standalone_expression(value); let value_ty = self.expression_ty(value); let name_ast_id = name.scoped_expression_id(self.db, self.scope()); - let target_ty = match assignment.target() { + let mut target_ty = match assignment.target() { TargetKind::Sequence(unpack) => { let unpacked = infer_unpack_types(self.db, unpack); // Only copy the diagnostics if this is the first assignment to avoid duplicating the @@ -1686,6 +1674,16 @@ impl<'db> TypeInferenceBuilder<'db> { TargetKind::Name => value_ty, }; + // NOTE: typing.Any is _not_ currently defined as a _SpecialForm in the typeshed, so we + // can't detect it in the same was a typing.NoReturn and friends. If the definition in the + // typeshed changes, this heuristic will need to change as well. + if let Some(known_instance) = file_to_module(self.db, definition.file(self.db)) + .as_ref() + .and_then(|module| KnownInstanceType::try_from_module_and_symbol(module, &name.id)) + { + target_ty = Type::KnownInstance(known_instance); + } + self.store_expression_type(name, target_ty); self.add_binding(name.into(), definition, target_ty); } From 62f432fd88a6ff158d117aa9d3c01c8adccf692e Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 3 Dec 2024 09:01:30 -0500 Subject: [PATCH 06/13] Remove unnecessary comments --- crates/red_knot_python_semantic/src/types.rs | 1 - crates/red_knot_python_semantic/src/types/infer.rs | 3 --- 2 files changed, 4 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 4c28a0f78c4ac..a851da29ff39f 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1956,7 +1956,6 @@ impl<'db> KnownInstanceType<'db> { Self::Union => KnownClass::SpecialForm, Self::NoReturn => KnownClass::SpecialForm, Self::Never => KnownClass::SpecialForm, - // typing.Any is _not_ currently defined as a _SpecialForm in the typeshed. Self::Any => KnownClass::Object, Self::TypeVar(_) => KnownClass::TypeVar, Self::TypeAliasType(_) => KnownClass::TypeAliasType, diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index e87947a8c50ff..0254573a2b667 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -1674,9 +1674,6 @@ impl<'db> TypeInferenceBuilder<'db> { TargetKind::Name => value_ty, }; - // NOTE: typing.Any is _not_ currently defined as a _SpecialForm in the typeshed, so we - // can't detect it in the same was a typing.NoReturn and friends. If the definition in the - // typeshed changes, this heuristic will need to change as well. if let Some(known_instance) = file_to_module(self.db, definition.file(self.db)) .as_ref() .and_then(|module| KnownInstanceType::try_from_module_and_symbol(module, &name.id)) From 0199648e064498bd0e01325512bd711190fc152b Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 3 Dec 2024 09:02:18 -0500 Subject: [PATCH 07/13] Don't need to look for typing_extensions.Any --- crates/red_knot_python_semantic/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index a851da29ff39f..0bf5d728f5779 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1976,7 +1976,7 @@ impl<'db> KnownInstanceType<'db> { return None; } match (module.name().as_str(), instance_name) { - ("typing" | "typing_extensions", "Any") => Some(Self::Any), + ("typing", "Any") => Some(Self::Any), ("typing" | "typing_extensions", "Literal") => Some(Self::Literal), ("typing" | "typing_extensions", "Optional") => Some(Self::Optional), ("typing" | "typing_extensions", "Union") => Some(Self::Union), From a2dd5a86da1b64b43f13ad5d33c8780ad3a677db Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 3 Dec 2024 14:38:37 -0500 Subject: [PATCH 08/13] Handle subclasses of Any correctly --- .../resources/mdtest/annotations/any.md | 20 +++++++++++++++++ crates/red_knot_python_semantic/src/types.rs | 22 ++++++++++++++++++- .../red_knot_python_semantic/src/types/mro.rs | 8 ++++++- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md index e637bb9a395ec..d09d01995fb63 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md @@ -46,3 +46,23 @@ def f(): # to that but not to our locally defined class. y: Any = "not an Any" # error: [invalid-assignment] ``` + +## Subclass + +The spec allows you to define subclasses of `Any`, which must also resolve to the Any type. + +```py +from typing import Any + +class Subclass(Any): + pass + +# Since Subclass is a subclass of Any, it is assignable to and from any other type, just like Any. +x: Subclass = 1 +y: int = Subclass() + +def f() -> Subclass: + pass + +reveal_type(f()) # revealed: Any +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 0bf5d728f5779..927aedb6e28de 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1356,6 +1356,14 @@ impl<'db> Type<'db> { } } + // If we instantiate a subclass of typing.Any, then we infer the result to be an + // instance of the Any type. + Type::ClassLiteral(ClassLiteralType { class }) + if class.is_subclass_of_base(db, ClassBase::Any) => + { + CallOutcome::callable(Type::Any) + } + // TODO annotated return type on `__new__` or metaclass `__call__` Type::ClassLiteral(ClassLiteralType { class }) => { CallOutcome::callable(match class.known(db) { @@ -1517,6 +1525,14 @@ impl<'db> Type<'db> { todo @ Type::Todo(_) => *todo, Type::Unknown => Type::Unknown, Type::Never => Type::Never, + // We consider an instance of any subclass of typing.Any to be an instance of the Any + // type. + Type::ClassLiteral(ClassLiteralType { class }) + | Type::SubclassOf(SubclassOfType { class }) + if class.is_subclass_of_base(db, ClassBase::Any) => + { + Type::Any + } Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(*class), Type::SubclassOf(SubclassOfType { class }) => Type::instance(*class), Type::Union(union) => union.map(db, |element| element.to_instance(db)), @@ -2644,7 +2660,11 @@ impl<'db> Class<'db> { pub fn is_subclass_of(self, db: &'db dyn Db, other: Class) -> bool { // `is_subclass_of` is checking the subtype relation, in which gradual types do not // participate, so we should not return `True` if we find `Any/Unknown` in the MRO. - self.iter_mro(db).contains(&ClassBase::Class(other)) + self.is_subclass_of_base(db, other) + } + + fn is_subclass_of_base>>(self, db: &'db dyn Db, other: C) -> bool { + self.iter_mro(db).contains(&other.into()) } /// Return the explicit `metaclass` of this class, if one is defined. diff --git a/crates/red_knot_python_semantic/src/types/mro.rs b/crates/red_knot_python_semantic/src/types/mro.rs index a21e7c5861975..83873d23d9242 100644 --- a/crates/red_knot_python_semantic/src/types/mro.rs +++ b/crates/red_knot_python_semantic/src/types/mro.rs @@ -377,8 +377,8 @@ impl<'db> ClassBase<'db> { | KnownInstanceType::Union | KnownInstanceType::NoReturn | KnownInstanceType::Never - | KnownInstanceType::Any | KnownInstanceType::Optional => None, + KnownInstanceType::Any => Some(Self::Any), }, } } @@ -406,6 +406,12 @@ impl<'db> ClassBase<'db> { } } +impl<'db> From> for ClassBase<'db> { + fn from(value: Class<'db>) -> Self { + ClassBase::Class(value) + } +} + impl<'db> From> for Type<'db> { fn from(value: ClassBase<'db>) -> Self { match value { From 4c0350507032ca68b869a45eab88ee0e9496cbf2 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 3 Dec 2024 16:24:39 -0500 Subject: [PATCH 09/13] Revert incorrect handling of Any as superclass --- .../resources/mdtest/annotations/any.md | 12 ++++++++---- crates/red_knot_python_semantic/src/types.rs | 16 ---------------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md index d09d01995fb63..ace366f820611 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md @@ -51,18 +51,22 @@ y: Any = "not an Any" # error: [invalid-assignment] The spec allows you to define subclasses of `Any`, which must also resolve to the Any type. +TODO: Handle assignments correctly. `Subclass` has an unknown superclass, which might be `int`. The +assignment to `x` should not be allowed, even when the unknown superclass is `int`. The assignment +to `y` should be allowed, since `Subclass` might have `int` as a superclass, and is therefore +assignable to `int`. + ```py from typing import Any class Subclass(Any): pass -# Since Subclass is a subclass of Any, it is assignable to and from any other type, just like Any. -x: Subclass = 1 -y: int = Subclass() +x: Subclass = 1 # error: [invalid-assignment] +y: int = Subclass() # error: [invalid-assignment] def f() -> Subclass: pass -reveal_type(f()) # revealed: Any +reveal_type(f()) # revealed: Subclass ``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 927aedb6e28de..bdae6ecab3530 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1356,14 +1356,6 @@ impl<'db> Type<'db> { } } - // If we instantiate a subclass of typing.Any, then we infer the result to be an - // instance of the Any type. - Type::ClassLiteral(ClassLiteralType { class }) - if class.is_subclass_of_base(db, ClassBase::Any) => - { - CallOutcome::callable(Type::Any) - } - // TODO annotated return type on `__new__` or metaclass `__call__` Type::ClassLiteral(ClassLiteralType { class }) => { CallOutcome::callable(match class.known(db) { @@ -1525,14 +1517,6 @@ impl<'db> Type<'db> { todo @ Type::Todo(_) => *todo, Type::Unknown => Type::Unknown, Type::Never => Type::Never, - // We consider an instance of any subclass of typing.Any to be an instance of the Any - // type. - Type::ClassLiteral(ClassLiteralType { class }) - | Type::SubclassOf(SubclassOfType { class }) - if class.is_subclass_of_base(db, ClassBase::Any) => - { - Type::Any - } Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(*class), Type::SubclassOf(SubclassOfType { class }) => Type::instance(*class), Type::Union(union) => union.map(db, |element| element.to_instance(db)), From b733211b7dfce3bc23d96584b011933bf877e780 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 3 Dec 2024 16:28:06 -0500 Subject: [PATCH 10/13] Use impl parameter type --- crates/red_knot_python_semantic/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index adec8fc70099e..55bd89002d4c0 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2658,7 +2658,7 @@ impl<'db> Class<'db> { self.is_subclass_of_base(db, other) } - fn is_subclass_of_base>>(self, db: &'db dyn Db, other: C) -> bool { + fn is_subclass_of_base(self, db: &'db dyn Db, other: impl Into>) -> bool { self.iter_mro(db).contains(&other.into()) } From d5b2d3bab69131c8c1b887696ebb5e3ed6dd5538 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 3 Dec 2024 16:31:08 -0500 Subject: [PATCH 11/13] Include basic mro test --- .../resources/mdtest/annotations/any.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md index ace366f820611..f6a1a182a4bfb 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md @@ -62,6 +62,8 @@ from typing import Any class Subclass(Any): pass +reveal_type(Subclass.__mro__) # revealed: tuple[Literal[Subclass], Any, Literal[object]] + x: Subclass = 1 # error: [invalid-assignment] y: int = Subclass() # error: [invalid-assignment] From 0ed09b60e39b3034b4c565bb1ee6675a8d50e59f Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 3 Dec 2024 16:44:19 -0500 Subject: [PATCH 12/13] Satisfy the linter --- .../resources/mdtest/annotations/any.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md index f6a1a182a4bfb..c038913702fa3 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md @@ -51,8 +51,8 @@ y: Any = "not an Any" # error: [invalid-assignment] The spec allows you to define subclasses of `Any`, which must also resolve to the Any type. -TODO: Handle assignments correctly. `Subclass` has an unknown superclass, which might be `int`. The -assignment to `x` should not be allowed, even when the unknown superclass is `int`. The assignment +TODO: Handle assignments correctly. `Subclass` has an unknown superclass, which might be `int`. The +assignment to `x` should not be allowed, even when the unknown superclass is `int`. The assignment to `y` should be allowed, since `Subclass` might have `int` as a superclass, and is therefore assignable to `int`. @@ -64,7 +64,7 @@ class Subclass(Any): reveal_type(Subclass.__mro__) # revealed: tuple[Literal[Subclass], Any, Literal[object]] -x: Subclass = 1 # error: [invalid-assignment] +x: Subclass = 1 # error: [invalid-assignment] y: int = Subclass() # error: [invalid-assignment] def f() -> Subclass: From 3cf67d50909d9f383506dd7c8e525dc35de92943 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Wed, 4 Dec 2024 09:48:08 -0500 Subject: [PATCH 13/13] Refine comments in mdtest --- .../resources/mdtest/annotations/any.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md index c038913702fa3..a1db55272c3bb 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md @@ -49,7 +49,7 @@ y: Any = "not an Any" # error: [invalid-assignment] ## Subclass -The spec allows you to define subclasses of `Any`, which must also resolve to the Any type. +The spec allows you to define subclasses of `Any`. TODO: Handle assignments correctly. `Subclass` has an unknown superclass, which might be `int`. The assignment to `x` should not be allowed, even when the unknown superclass is `int`. The assignment @@ -65,6 +65,7 @@ class Subclass(Any): reveal_type(Subclass.__mro__) # revealed: tuple[Literal[Subclass], Any, Literal[object]] x: Subclass = 1 # error: [invalid-assignment] +# TODO: no diagnostic y: int = Subclass() # error: [invalid-assignment] def f() -> Subclass: