diff --git a/crates/ty_python_semantic/resources/mdtest/call/methods.md b/crates/ty_python_semantic/resources/mdtest/call/methods.md index 91f3412e11..f4bd44749e 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/methods.md +++ b/crates/ty_python_semantic/resources/mdtest/call/methods.md @@ -621,9 +621,6 @@ static_assert(is_assignable_to(TypeOf[f], Callable)) # revealed: reveal_type(f.__get__) - -# TODO: should pass -# error: [static-assert-error] static_assert(is_assignable_to(TypeOf[f.__get__], Callable)) # revealed: def __call__(self, *args: Any, **kwargs: Any) -> Any @@ -632,9 +629,6 @@ static_assert(is_assignable_to(TypeOf[types.FunctionType.__call__], Callable)) # revealed: reveal_type(f.__call__) - -# TODO: should pass -# error: [static-assert-error] static_assert(is_assignable_to(TypeOf[f.__call__], Callable)) # revealed: @@ -651,9 +645,6 @@ static_assert(not is_assignable_to(TypeOf[MyClass.my_property], Callable)) # revealed: reveal_type(MyClass.my_property.__get__) - -# TODO: should pass -# error: [static-assert-error] static_assert(is_assignable_to(TypeOf[MyClass.my_property.__get__], Callable)) # revealed: @@ -665,9 +656,6 @@ static_assert(is_assignable_to(TypeOf[property.__set__], Callable)) # revealed: reveal_type(MyClass.my_property.__set__) - -# TODO: should pass -# error: [static-assert-error] static_assert(is_assignable_to(TypeOf[MyClass.my_property.__set__], Callable)) # revealed: def startswith(self, prefix: str | tuple[str, ...], start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool @@ -676,9 +664,6 @@ static_assert(is_assignable_to(TypeOf[str.startswith], Callable)) # revealed: reveal_type("foo".startswith) - -# TODO: should pass -# error: [static-assert-error] static_assert(is_assignable_to(TypeOf["foo".startswith], Callable)) def _( @@ -686,8 +671,7 @@ def _( b: CallableTypeOf[f], c: CallableTypeOf[f.__get__], d: CallableTypeOf[types.FunctionType.__call__], - # TODO: false-positive diagnostic - e: CallableTypeOf[f.__call__], # error: [invalid-type-form] + e: CallableTypeOf[f.__call__], f: CallableTypeOf[property], g: CallableTypeOf[property.__get__], h: CallableTypeOf[MyClass.my_property.__get__], @@ -709,8 +693,7 @@ def _( # revealed: (self, *args: Any, **kwargs: Any) -> Any reveal_type(d) - # TODO: this should be `(obj: type) -> None` - # revealed: Unknown + # revealed: (obj: type) -> None reveal_type(e) # revealed: (fget: ((Any, /) -> Any) | None = None, fset: ((Any, Any, /) -> None) | None = None, fdel: ((Any, /) -> Any) | None = None, doc: str | None = None) -> Unknown diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index e3623f48d2..adbda0bb5b 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1321,6 +1321,12 @@ impl<'db> Type<'db> { Type::TypeAlias(alias) => alias.value_type(db).into_callable(db), + Type::KnownBoundMethod(method) => Some(Type::Callable(CallableType::new( + db, + CallableSignature::from_overloads(method.signatures(db)), + false, + ))), + Type::Never | Type::DataclassTransformer(_) | Type::AlwaysTruthy @@ -1334,8 +1340,7 @@ impl<'db> Type<'db> { | Type::TypedDict(_) => None, // TODO - Type::KnownBoundMethod(_) - | Type::WrapperDescriptor(_) + Type::WrapperDescriptor(_) | Type::DataclassDecorator(_) | Type::ModuleLiteral(_) | Type::SpecialForm(_) @@ -4009,54 +4014,8 @@ impl<'db> Type<'db> { .into() } - Type::KnownBoundMethod( - KnownBoundMethodType::FunctionTypeDunderGet(_) - | KnownBoundMethodType::PropertyDunderGet(_), - ) => { - // Here, we dynamically model the overloaded function signature of `types.FunctionType.__get__`. - // This is required because we need to return more precise types than what the signature in - // typeshed provides: - // - // ```py - // class FunctionType: - // # ... - // @overload - // def __get__(self, instance: None, owner: type, /) -> FunctionType: ... - // @overload - // def __get__(self, instance: object, owner: type | None = None, /) -> MethodType: ... - // ``` - // - // For `builtins.property.__get__`, we use the same signature. The return types are not - // specified yet, they will be dynamically added in `Bindings::evaluate_known_cases`. - - CallableBinding::from_overloads( - self, - [ - Signature::new( - Parameters::new([ - Parameter::positional_only(Some(Name::new_static("instance"))) - .with_annotated_type(Type::none(db)), - Parameter::positional_only(Some(Name::new_static("owner"))) - .with_annotated_type(KnownClass::Type.to_instance(db)), - ]), - None, - ), - Signature::new( - Parameters::new([ - Parameter::positional_only(Some(Name::new_static("instance"))) - .with_annotated_type(Type::object(db)), - Parameter::positional_only(Some(Name::new_static("owner"))) - .with_annotated_type(UnionType::from_elements( - db, - [KnownClass::Type.to_instance(db), Type::none(db)], - )) - .with_default_type(Type::none(db)), - ]), - None, - ), - ], - ) - .into() + Type::KnownBoundMethod(method) => { + CallableBinding::from_overloads(self, method.signatures(db)).into() } Type::WrapperDescriptor( @@ -4115,20 +4074,6 @@ impl<'db> Type<'db> { .into() } - Type::KnownBoundMethod(KnownBoundMethodType::PropertyDunderSet(_)) => Binding::single( - self, - Signature::new( - Parameters::new([ - Parameter::positional_only(Some(Name::new_static("instance"))) - .with_annotated_type(Type::object(db)), - Parameter::positional_only(Some(Name::new_static("value"))) - .with_annotated_type(Type::object(db)), - ]), - None, - ), - ) - .into(), - Type::WrapperDescriptor(WrapperDescriptorKind::PropertyDunderSet) => Binding::single( self, Signature::new( @@ -4145,36 +4090,6 @@ impl<'db> Type<'db> { ) .into(), - Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(_)) => Binding::single( - self, - Signature::new( - Parameters::new([ - Parameter::positional_only(Some(Name::new_static("prefix"))) - .with_annotated_type(UnionType::from_elements( - db, - [ - KnownClass::Str.to_instance(db), - Type::homogeneous_tuple(db, KnownClass::Str.to_instance(db)), - ], - )), - Parameter::positional_only(Some(Name::new_static("start"))) - .with_annotated_type(UnionType::from_elements( - db, - [KnownClass::SupportsIndex.to_instance(db), Type::none(db)], - )) - .with_default_type(Type::none(db)), - Parameter::positional_only(Some(Name::new_static("end"))) - .with_annotated_type(UnionType::from_elements( - db, - [KnownClass::SupportsIndex.to_instance(db), Type::none(db)], - )) - .with_default_type(Type::none(db)), - ]), - Some(KnownClass::Bool.to_instance(db)), - ), - ) - .into(), - // TODO: We should probably also check the original return type of the function // that was decorated with `@dataclass_transform`, to see if it is consistent with // with what we configure here. @@ -4906,10 +4821,8 @@ impl<'db> Type<'db> { Binding::single(self, Signature::todo("Type::Intersection.call()")).into() } - // TODO: these are actually callable - Type::KnownBoundMethod(_) | Type::DataclassDecorator(_) => { - CallableBinding::not_callable(self).into() - } + // TODO: this is actually callable + Type::DataclassDecorator(_) => CallableBinding::not_callable(self).into(), // TODO: some `SpecialForm`s are callable (e.g. TypedDicts) Type::SpecialForm(_) => CallableBinding::not_callable(self).into(), @@ -9483,6 +9396,98 @@ impl<'db> KnownBoundMethodType<'db> { KnownBoundMethodType::StrStartswith(_) => KnownClass::BuiltinFunctionType, } } + + /// Return the signatures of this bound method type. + /// + /// If the bound method type is overloaded, it may have multiple signatures. + fn signatures(self, db: &'db dyn Db) -> impl Iterator> { + match self { + // Here, we dynamically model the overloaded function signature of `types.FunctionType.__get__`. + // This is required because we need to return more precise types than what the signature in + // typeshed provides: + // + // ```py + // class FunctionType: + // # ... + // @overload + // def __get__(self, instance: None, owner: type, /) -> FunctionType: ... + // @overload + // def __get__(self, instance: object, owner: type | None = None, /) -> MethodType: ... + // ``` + // + // For `builtins.property.__get__`, we use the same signature. The return types are not + // specified yet, they will be dynamically added in `Bindings::evaluate_known_cases`. + KnownBoundMethodType::FunctionTypeDunderGet(_) + | KnownBoundMethodType::PropertyDunderGet(_) => Either::Left(Either::Left( + [ + Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("instance"))) + .with_annotated_type(Type::none(db)), + Parameter::positional_only(Some(Name::new_static("owner"))) + .with_annotated_type(KnownClass::Type.to_instance(db)), + ]), + None, + ), + Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("instance"))) + .with_annotated_type(Type::object(db)), + Parameter::positional_only(Some(Name::new_static("owner"))) + .with_annotated_type(UnionType::from_elements( + db, + [KnownClass::Type.to_instance(db), Type::none(db)], + )) + .with_default_type(Type::none(db)), + ]), + None, + ), + ] + .into_iter(), + )), + KnownBoundMethodType::FunctionTypeDunderCall(function) => Either::Left(Either::Right( + function.signature(db).overloads.iter().cloned(), + )), + KnownBoundMethodType::PropertyDunderSet(_) => { + Either::Right(std::iter::once(Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("instance"))) + .with_annotated_type(Type::object(db)), + Parameter::positional_only(Some(Name::new_static("value"))) + .with_annotated_type(Type::object(db)), + ]), + None, + ))) + } + KnownBoundMethodType::StrStartswith(_) => { + Either::Right(std::iter::once(Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("prefix"))) + .with_annotated_type(UnionType::from_elements( + db, + [ + KnownClass::Str.to_instance(db), + Type::homogeneous_tuple(db, KnownClass::Str.to_instance(db)), + ], + )), + Parameter::positional_only(Some(Name::new_static("start"))) + .with_annotated_type(UnionType::from_elements( + db, + [KnownClass::SupportsIndex.to_instance(db), Type::none(db)], + )) + .with_default_type(Type::none(db)), + Parameter::positional_only(Some(Name::new_static("end"))) + .with_annotated_type(UnionType::from_elements( + db, + [KnownClass::SupportsIndex.to_instance(db), Type::none(db)], + )) + .with_default_type(Type::none(db)), + ]), + Some(KnownClass::Bool.to_instance(db)), + ))) + } + } + } } /// Represents a specific instance of `types.WrapperDescriptorType`