[ty] Completely ignore typeshed's stub for Any (#20079)

This commit is contained in:
Alex Waygood 2025-08-25 15:27:55 +01:00 committed by GitHub
parent d0bcf56bd9
commit a04823cfad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 134 additions and 105 deletions

View file

@ -137,6 +137,22 @@ from unittest.mock import MagicMock
x: int = MagicMock()
```
## Runtime properties
`typing.Any` is a class at runtime on Python 3.11+, and `typing_extensions.Any` is always a class.
On earlier versions of Python, `typing.Any` was an instance of `typing._SpecialForm`, but this is
not currently modeled by ty. We currently infer `Any` has having all attributes a class would have
on all versions of Python:
```py
from typing import Any
from ty_extensions import TypeOf, static_assert, is_assignable_to
reveal_type(Any.__base__) # revealed: type | None
reveal_type(Any.__bases__) # revealed: tuple[type, ...]
static_assert(is_assignable_to(TypeOf[Any], type))
```
## Invalid
`Any` cannot be parameterized:
@ -144,7 +160,32 @@ x: int = MagicMock()
```py
from typing import Any
# error: [invalid-type-form] "Type `typing.Any` expected no type parameter"
# error: [invalid-type-form] "Special form `typing.Any` expected no type parameter"
def f(x: Any[int]):
reveal_type(x) # revealed: Unknown
```
`Any` cannot be called (this leads to a `TypeError` at runtime):
```py
Any() # error: [call-non-callable] "Object of type `typing.Any` is not callable"
```
`Any` also cannot be used as a metaclass (under the hood, this leads to an implicit call to `Any`):
```py
class F(metaclass=Any): ... # error: [invalid-metaclass] "Metaclass type `typing.Any` is not callable"
```
And `Any` cannot be used in `isinstance()` checks:
```py
# error: [invalid-argument-type] "`typing.Any` cannot be used with `isinstance()`: This call will raise `TypeError` at runtime"
isinstance("", Any)
```
But `issubclass()` checks are fine:
```py
issubclass(object, Any) # no error!
```

View file

@ -221,11 +221,11 @@ static_assert(is_assignable_to(type[Unknown], Meta))
static_assert(is_assignable_to(Meta, type[Any]))
static_assert(is_assignable_to(Meta, type[Unknown]))
class AnyMeta(metaclass=Any): ...
static_assert(is_assignable_to(type[AnyMeta], type))
static_assert(is_assignable_to(type[AnyMeta], type[object]))
static_assert(is_assignable_to(type[AnyMeta], type[Any]))
def _(x: Any):
class AnyMeta(metaclass=x): ...
static_assert(is_assignable_to(type[AnyMeta], type))
static_assert(is_assignable_to(type[AnyMeta], type[object]))
static_assert(is_assignable_to(type[AnyMeta], type[Any]))
from typing import TypeVar, Generic, Any

View file

@ -5528,7 +5528,6 @@ impl<'db> Type<'db> {
// https://typing.python.org/en/latest/spec/special-types.html#special-cases-for-float-and-complex
Type::ClassLiteral(class) => {
let ty = match class.known(db) {
Some(KnownClass::Any) => Type::any(),
Some(KnownClass::Complex) => UnionType::from_elements(
db,
[
@ -5622,6 +5621,7 @@ impl<'db> Type<'db> {
Type::SpecialForm(special_form) => match special_form {
SpecialFormType::Never | SpecialFormType::NoReturn => Ok(Type::Never),
SpecialFormType::LiteralString => Ok(Type::LiteralString),
SpecialFormType::Any => Ok(Type::any()),
SpecialFormType::Unknown => Ok(Type::unknown()),
SpecialFormType::AlwaysTruthy => Ok(Type::AlwaysTruthy),
SpecialFormType::AlwaysFalsy => Ok(Type::AlwaysFalsy),

View file

@ -3485,7 +3485,6 @@ pub enum KnownClass {
// Typeshed
NoneType, // Part of `types` for Python >= 3.10
// Typing
Any,
Awaitable,
Generator,
Deprecated,
@ -3568,8 +3567,7 @@ impl KnownClass {
Self::NoneType => Some(Truthiness::AlwaysFalse),
Self::Any
| Self::BaseException
Self::BaseException
| Self::Exception
| Self::ExceptionGroup
| Self::Object
@ -3689,7 +3687,6 @@ impl KnownClass {
// Anything with a *runtime* MRO (N.B. sometimes different from the MRO that typeshed gives!)
// with length >2, or anything that is implemented in pure Python, is not a solid base.
Self::ABCMeta
| Self::Any
| Self::Awaitable
| Self::Generator
| Self::Enum
@ -3762,7 +3759,6 @@ impl KnownClass {
| KnownClass::AsyncGeneratorType
| KnownClass::CoroutineType
| KnownClass::NoneType
| KnownClass::Any
| KnownClass::StdlibAlias
| KnownClass::SpecialForm
| KnownClass::TypeVar
@ -3839,7 +3835,6 @@ impl KnownClass {
| KnownClass::AsyncGeneratorType
| KnownClass::CoroutineType
| KnownClass::NoneType
| KnownClass::Any
| KnownClass::StdlibAlias
| KnownClass::SpecialForm
| KnownClass::TypeVar
@ -3916,7 +3911,6 @@ impl KnownClass {
| KnownClass::AsyncGeneratorType
| KnownClass::CoroutineType
| KnownClass::NoneType
| KnownClass::Any
| KnownClass::StdlibAlias
| KnownClass::SpecialForm
| KnownClass::TypeVar
@ -3967,8 +3961,7 @@ impl KnownClass {
| Self::NamedTupleLike
| Self::Generator => true,
Self::Any
| Self::Bool
Self::Bool
| Self::Object
| Self::Bytes
| Self::Bytearray
@ -4037,7 +4030,6 @@ impl KnownClass {
pub(crate) fn name(self, db: &dyn Db) -> &'static str {
match self {
Self::Any => "Any",
Self::Bool => "bool",
Self::Object => "object",
Self::Bytes => "bytes",
@ -4347,8 +4339,7 @@ impl KnownClass {
| Self::UnionType
| Self::WrapperDescriptorType => KnownModule::Types,
Self::NoneType => KnownModule::Typeshed,
Self::Any
| Self::Awaitable
Self::Awaitable
| Self::Generator
| Self::SpecialForm
| Self::TypeVar
@ -4409,8 +4400,7 @@ impl KnownClass {
| Self::UnionType
| Self::NotImplementedType => Some(true),
Self::Any
| Self::Bool
Self::Bool
| Self::Object
| Self::Bytes
| Self::Bytearray
@ -4489,8 +4479,7 @@ impl KnownClass {
| Self::TypeAliasType
| Self::NotImplementedType => true,
Self::Any
| Self::Bool
Self::Bool
| Self::Object
| Self::Bytes
| Self::Bytearray
@ -4565,7 +4554,6 @@ impl 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,
@ -4655,8 +4643,7 @@ impl KnownClass {
/// Return `true` if the module of `self` matches `module`
fn check_module(self, db: &dyn Db, module: KnownModule) -> bool {
match self {
Self::Any
| Self::Bool
Self::Bool
| Self::Object
| Self::Bytes
| Self::Bytearray

View file

@ -77,13 +77,7 @@ impl<'db> ClassBase<'db> {
) -> Option<Self> {
match ty {
Type::Dynamic(dynamic) => Some(Self::Dynamic(dynamic)),
Type::ClassLiteral(literal) => {
if literal.is_known(db, KnownClass::Any) {
Some(Self::Dynamic(DynamicType::Any))
} else {
Some(Self::Class(literal.default_specialization(db)))
}
}
Type::ClassLiteral(literal) => Some(Self::Class(literal.default_specialization(db))),
Type::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))),
Type::NominalInstance(instance)
if instance.class(db).is_known(db, KnownClass::GenericAlias) =>
@ -201,6 +195,7 @@ impl<'db> ClassBase<'db> {
| SpecialFormType::AlwaysTruthy
| SpecialFormType::AlwaysFalsy => None,
SpecialFormType::Any => Some(Self::Dynamic(DynamicType::Any)),
SpecialFormType::Unknown => Some(Self::unknown()),
SpecialFormType::Protocol => Some(Self::Protocol),

View file

@ -76,9 +76,6 @@ impl Display for DisplayType<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let representation = self.ty.representation(self.db, self.settings);
match self.ty {
Type::ClassLiteral(literal) if literal.is_known(self.db, KnownClass::Any) => {
write!(f, "typing.Any")
}
Type::IntLiteral(_)
| Type::BooleanLiteral(_)
| Type::StringLiteral(_)

View file

@ -68,7 +68,7 @@ use crate::types::call::{Binding, CallArguments};
use crate::types::constraints::Constraints;
use crate::types::context::InferContext;
use crate::types::diagnostic::{
REDUNDANT_CAST, STATIC_ASSERT_ERROR, TYPE_ASSERTION_FAILURE,
INVALID_ARGUMENT_TYPE, REDUNDANT_CAST, STATIC_ASSERT_ERROR, TYPE_ASSERTION_FAILURE,
report_bad_argument_to_get_protocol_members, report_bad_argument_to_protocol_interface,
report_runtime_check_against_non_runtime_checkable_protocol,
};
@ -79,8 +79,8 @@ use crate::types::visitor::any_over_type;
use crate::types::{
BoundMethodType, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, ClassType,
DeprecatedInstance, DynamicType, HasRelationToVisitor, IsEquivalentVisitor, KnownClass,
NormalizedVisitor, Truthiness, Type, TypeMapping, TypeRelation, UnionBuilder, all_members,
walk_type_mapping,
NormalizedVisitor, SpecialFormType, Truthiness, Type, TypeMapping, TypeRelation, UnionBuilder,
all_members, walk_type_mapping,
};
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
@ -1454,25 +1454,48 @@ impl KnownFunction {
}
KnownFunction::IsInstance | KnownFunction::IsSubclass => {
let [Some(first_arg), Some(Type::ClassLiteral(class))] = parameter_types else {
let [Some(first_arg), Some(second_argument)] = parameter_types else {
return;
};
if let Some(protocol_class) = class.into_protocol_class(db) {
if !protocol_class.is_runtime_checkable(db) {
report_runtime_check_against_non_runtime_checkable_protocol(
context,
call_expression,
protocol_class,
self,
);
}
}
match second_argument {
Type::ClassLiteral(class) => {
if let Some(protocol_class) = class.into_protocol_class(db) {
if !protocol_class.is_runtime_checkable(db) {
report_runtime_check_against_non_runtime_checkable_protocol(
context,
call_expression,
protocol_class,
self,
);
}
}
if self == KnownFunction::IsInstance {
overload.set_return_type(
is_instance_truthiness(db, *first_arg, *class).into_type(db),
);
if self == KnownFunction::IsInstance {
overload.set_return_type(
is_instance_truthiness(db, *first_arg, *class).into_type(db),
);
}
}
// The special-casing here is necessary because we recognise the symbol `typing.Any` as an
// instance of `type` at runtime. Even once we understand typeshed's annotation for
// `isinstance()`, we'd continue to accept calls such as `isinstance(x, typing.Any)` without
// emitting a diagnostic if we didn't have this branch.
Type::SpecialForm(SpecialFormType::Any)
if self == KnownFunction::IsInstance =>
{
let Some(builder) =
context.report_lint(&INVALID_ARGUMENT_TYPE, call_expression)
else {
return;
};
let mut diagnostic = builder.into_diagnostic(format_args!(
"`typing.Any` cannot be used with `isinstance()`"
));
diagnostic
.set_primary_message("This call will raise `TypeError` at runtime");
}
_ => {}
}
}

View file

@ -3080,15 +3080,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let maybe_known_class = KnownClass::try_from_file_and_name(self.db(), self.file(), name);
let ty = if maybe_known_class.is_none()
&& &name.id == "NamedTuple"
&& matches!(
let in_typing_module = || {
matches!(
file_to_module(self.db(), self.file()).and_then(|module| module.known(self.db())),
Some(KnownModule::Typing | KnownModule::TypingExtensions)
) {
Type::SpecialForm(SpecialFormType::NamedTuple)
} else {
Type::from(ClassLiteral::new(
)
};
let ty = match (maybe_known_class, &*name.id) {
(None, "NamedTuple") if in_typing_module() => {
Type::SpecialForm(SpecialFormType::NamedTuple)
}
(None, "Any") if in_typing_module() => Type::SpecialForm(SpecialFormType::Any),
_ => Type::from(ClassLiteral::new(
self.db(),
name.id.clone(),
body_scope,
@ -3096,7 +3100,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
deprecated,
dataclass_params,
dataclass_transformer_params,
))
)),
};
self.add_declaration_with_binding(
@ -10242,9 +10246,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
let name_ty = self.infer_expression(slice);
match name_ty {
Type::ClassLiteral(class_literal) => {
if class_literal.is_known(self.db(), KnownClass::Any) {
SubclassOfType::subclass_of_any()
} else if class_literal.is_protocol(self.db()) {
if class_literal.is_protocol(self.db()) {
SubclassOfType::from(
self.db(),
todo_type!("type[T] for protocols").expect_dynamic(),
@ -10256,6 +10258,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
)
}
}
Type::SpecialForm(SpecialFormType::Any) => SubclassOfType::subclass_of_any(),
Type::SpecialForm(SpecialFormType::Unknown) => {
SubclassOfType::subclass_of_unknown()
}
@ -10339,13 +10342,6 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
self.infer_expression(&subscript.slice);
Type::unknown()
}
Type::ClassLiteral(literal) if literal.is_known(self.db(), KnownClass::Any) => {
self.infer_expression(slice);
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
builder.into_diagnostic("Type `typing.Any` expected no type parameter");
}
Type::unknown()
}
Type::SpecialForm(special_form) => {
self.infer_parameterized_special_form_type_expression(subscript, special_form)
}
@ -10919,6 +10915,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
| SpecialFormType::TypeAlias
| SpecialFormType::TypedDict
| SpecialFormType::Unknown
| SpecialFormType::Any
| SpecialFormType::NamedTuple => {
self.infer_type_expression(arguments_slice);

View file

@ -12,7 +12,7 @@ use crate::types::enums::is_single_member_enum;
use crate::types::protocol_class::walk_protocol_interface;
use crate::types::tuple::{TupleSpec, TupleType};
use crate::types::{
ApplyTypeMappingVisitor, ClassBase, DynamicType, HasRelationToVisitor, IsDisjointVisitor,
ApplyTypeMappingVisitor, ClassBase, HasRelationToVisitor, IsDisjointVisitor,
IsEquivalentVisitor, NormalizedVisitor, TypeMapping, TypeRelation, VarianceInferable,
};
use crate::{Db, FxOrderSet};
@ -23,20 +23,20 @@ impl<'db> Type<'db> {
pub(crate) fn instance(db: &'db dyn Db, class: ClassType<'db>) -> Self {
let (class_literal, specialization) = class.class_literal(db);
match class_literal.known(db) {
Some(KnownClass::Any) => Type::Dynamic(DynamicType::Any),
Some(KnownClass::Tuple) => Type::tuple(TupleType::new(
if class_literal.is_known(db, KnownClass::Tuple) {
Type::tuple(TupleType::new(
db,
specialization
.and_then(|spec| Some(Cow::Borrowed(spec.tuple(db)?)))
.unwrap_or_else(|| Cow::Owned(TupleSpec::homogeneous(Type::unknown())))
.as_ref(),
)),
_ if class_literal.is_protocol(db) => {
Self::ProtocolInstance(ProtocolInstanceType::from_class(class))
}
_ if class_literal.is_typed_dict(db) => Type::typed_dict(class),
_ => Type::non_tuple_instance(class),
))
} else if class_literal.is_protocol(db) {
Self::ProtocolInstance(ProtocolInstanceType::from_class(class))
} else if class_literal.is_typed_dict(db) {
Type::typed_dict(class)
} else {
Type::non_tuple_instance(class)
}
}

View file

@ -183,15 +183,7 @@ impl ClassInfoConstraintFunction {
match classinfo {
Type::TypeAlias(alias) => self.generate_constraint(db, alias.value_type(db)),
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.is_known(db, KnownClass::Any) {
None
} else {
Some(constraint_fn(class_literal))
}
}
Type::ClassLiteral(class_literal) => Some(constraint_fn(class_literal)),
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
SubclassOfInner::Class(ClassType::NonGeneric(class)) => Some(constraint_fn(class)),
// It's not valid to use a generic alias as the second argument to `isinstance()` or `issubclass()`,

View file

@ -27,6 +27,7 @@ use std::str::FromStr;
get_size2::GetSize,
)]
pub enum SpecialFormType {
Any,
/// The symbol `typing.Annotated` (which can also be found as `typing_extensions.Annotated`)
Annotated,
/// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`)
@ -162,7 +163,7 @@ impl SpecialFormType {
| Self::Protocol // actually `_ProtocolMeta` at runtime but this is what typeshed says
| Self::ReadOnly => KnownClass::SpecialForm,
Self::Generic => KnownClass::Type,
Self::Generic | Self::Any => KnownClass::Type,
Self::List
| Self::Dict
@ -245,6 +246,7 @@ impl SpecialFormType {
| Self::TypingSelf
| Self::Protocol
| Self::NamedTuple
| Self::Any
| Self::ReadOnly => {
matches!(module, KnownModule::Typing | KnownModule::TypingExtensions)
}
@ -317,6 +319,7 @@ impl SpecialFormType {
| Self::TypeIs
| Self::ReadOnly
| Self::Protocol
| Self::Any
| Self::Generic => false,
}
}
@ -324,6 +327,7 @@ impl SpecialFormType {
/// Return the repr of the symbol at runtime
pub(super) const fn repr(self) -> &'static str {
match self {
SpecialFormType::Any => "typing.Any",
SpecialFormType::Annotated => "typing.Annotated",
SpecialFormType::Literal => "typing.Literal",
SpecialFormType::LiteralString => "typing.LiteralString",

View file

@ -7,7 +7,7 @@ use crate::types::variance::VarianceInferable;
use crate::types::{
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, ClassType, DynamicType,
HasRelationToVisitor, IsDisjointVisitor, KnownClass, MemberLookupPolicy, NormalizedVisitor,
Type, TypeMapping, TypeRelation, TypeVarInstance,
SpecialFormType, Type, TypeMapping, TypeRelation, TypeVarInstance,
};
use crate::{Db, FxOrderSet};
@ -47,14 +47,10 @@ impl<'db> SubclassOfType<'db> {
SubclassOfInner::Class(class) => {
if class.is_final(db) {
Type::from(class)
} else if class.is_object(db) {
KnownClass::Type.to_instance(db)
} else {
match class.known(db) {
Some(KnownClass::Object) => KnownClass::Type.to_instance(db),
Some(KnownClass::Any) => Type::SubclassOf(Self {
subclass_of: SubclassOfInner::Dynamic(DynamicType::Any),
}),
_ => Type::SubclassOf(Self { subclass_of }),
}
Type::SubclassOf(Self { subclass_of })
}
}
}
@ -288,12 +284,9 @@ impl<'db> SubclassOfInner<'db> {
pub(crate) 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(if literal.is_known(db, KnownClass::Any) {
Self::Dynamic(DynamicType::Any)
} else {
Self::Class(literal.default_specialization(db))
}),
Type::ClassLiteral(literal) => Some(Self::Class(literal.default_specialization(db))),
Type::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))),
Type::SpecialForm(SpecialFormType::Any) => Some(Self::Dynamic(DynamicType::Any)),
_ => None,
}
}