mirror of https://github.com/astral-sh/ruff
[ty] Substitute for `typing.Self` when checking protocol members (#21569)
This patch updates our protocol assignability checks to substitute for any occurrences of `typing.Self` in method signatures, replacing it with the class being checked for assignability against the protocol. This requires a new helper method on signatures, `apply_self`, which substitutes occurrences of `typing.Self` _without_ binding the `self` parameter. We also update the `try_upcast_to_callable` method. Before, it would return a `Type`, since certain types upcast to a _union_ of callables, not to a single callable. However, even in that case, we know that every element of the union is a callable. We now return a vector of `CallableType`. (Actually a smallvec to handle the most common case of a single callable; and wrapped in a new type so that we can provide helper methods.) If there is more than one element in the result, it represents a union of callables. This lets callers get at the `CallableType` instances in a more type-safe way. (This makes it easier for our protocol checking code to call the new `apply_self` helper.) We also provide an `into_type` method so that callers that really do want a `Type` can get the original result easily. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
68343e7edf
commit
03fe560164
|
|
@ -2003,6 +2003,7 @@ python-version = "3.12"
|
|||
```
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
from typing_extensions import TypeVar, Self, Protocol
|
||||
from ty_extensions import is_equivalent_to, static_assert, is_assignable_to, is_subtype_of
|
||||
|
||||
|
|
@ -2094,6 +2095,13 @@ class NominalReturningSelfNotGeneric:
|
|||
def g(self) -> "NominalReturningSelfNotGeneric":
|
||||
return self
|
||||
|
||||
@final
|
||||
class Other: ...
|
||||
|
||||
class NominalReturningOtherClass:
|
||||
def g(self) -> Other:
|
||||
raise NotImplementedError
|
||||
|
||||
# TODO: should pass
|
||||
static_assert(is_equivalent_to(LegacyFunctionScoped, NewStyleFunctionScoped)) # error: [static-assert-error]
|
||||
|
||||
|
|
@ -2112,8 +2120,7 @@ static_assert(not is_assignable_to(NominalLegacy, UsesSelf))
|
|||
static_assert(not is_assignable_to(NominalWithSelf, NewStyleFunctionScoped))
|
||||
static_assert(not is_assignable_to(NominalWithSelf, LegacyFunctionScoped))
|
||||
static_assert(is_assignable_to(NominalWithSelf, UsesSelf))
|
||||
# TODO: should pass
|
||||
static_assert(is_subtype_of(NominalWithSelf, UsesSelf)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(NominalWithSelf, UsesSelf))
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(not is_assignable_to(NominalNotGeneric, NewStyleFunctionScoped)) # error: [static-assert-error]
|
||||
|
|
@ -2126,6 +2133,8 @@ static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, LegacyFunctio
|
|||
# TODO: should pass
|
||||
static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, UsesSelf)) # error: [static-assert-error]
|
||||
|
||||
static_assert(not is_assignable_to(NominalReturningOtherClass, UsesSelf))
|
||||
|
||||
# These test cases are taken from the typing conformance suite:
|
||||
class ShapeProtocolImplicitSelf(Protocol):
|
||||
def set_scale(self, scale: float) -> Self: ...
|
||||
|
|
|
|||
|
|
@ -1376,9 +1376,7 @@ mod implicit_globals {
|
|||
use crate::place::{Definedness, PlaceAndQualifiers, TypeOrigin};
|
||||
use crate::semantic_index::symbol::Symbol;
|
||||
use crate::semantic_index::{place_table, use_def_map};
|
||||
use crate::types::{
|
||||
CallableType, KnownClass, MemberLookupPolicy, Parameter, Parameters, Signature, Type,
|
||||
};
|
||||
use crate::types::{KnownClass, MemberLookupPolicy, Parameter, Parameters, Signature, Type};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use super::{Place, place_from_declarations};
|
||||
|
|
@ -1461,7 +1459,7 @@ mod implicit_globals {
|
|||
)),
|
||||
);
|
||||
Place::Defined(
|
||||
CallableType::function_like(db, signature),
|
||||
Type::function_like_callable(db, signature),
|
||||
TypeOrigin::Inferred,
|
||||
Definedness::PossiblyUndefined,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ use crate::semantic_index::predicate::{
|
|||
Predicates, ScopedPredicateId,
|
||||
};
|
||||
use crate::types::{
|
||||
IntersectionBuilder, Truthiness, Type, TypeContext, UnionBuilder, UnionType,
|
||||
CallableTypes, IntersectionBuilder, Truthiness, Type, TypeContext, UnionBuilder, UnionType,
|
||||
infer_expression_type, static_expression_truthiness,
|
||||
};
|
||||
|
||||
|
|
@ -871,8 +871,10 @@ impl ReachabilityConstraints {
|
|||
return Truthiness::AlwaysFalse.negate_if(!predicate.is_positive);
|
||||
}
|
||||
|
||||
let overloads_iterator =
|
||||
if let Some(Type::Callable(callable)) = ty.try_upcast_to_callable(db) {
|
||||
let overloads_iterator = if let Some(callable) = ty
|
||||
.try_upcast_to_callable(db)
|
||||
.and_then(CallableTypes::exactly_one)
|
||||
{
|
||||
callable.signatures(db).overloads.iter()
|
||||
} else {
|
||||
return Truthiness::AlwaysFalse.negate_if(!predicate.is_positive);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ use ruff_db::files::File;
|
|||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
|
||||
use type_ordering::union_or_intersection_elements_ordering;
|
||||
|
||||
|
|
@ -1533,17 +1534,20 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_upcast_to_callable(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
pub(crate) fn try_upcast_to_callable(self, db: &'db dyn Db) -> Option<CallableTypes<'db>> {
|
||||
match self {
|
||||
Type::Callable(_) => Some(self),
|
||||
Type::Callable(callable) => Some(CallableTypes::one(callable)),
|
||||
|
||||
Type::Dynamic(_) => Some(CallableType::function_like(db, Signature::dynamic(self))),
|
||||
Type::Dynamic(_) => Some(CallableTypes::one(CallableType::function_like(
|
||||
db,
|
||||
Signature::dynamic(self),
|
||||
))),
|
||||
|
||||
Type::FunctionLiteral(function_literal) => {
|
||||
Some(Type::Callable(function_literal.into_callable_type(db)))
|
||||
Some(CallableTypes::one(function_literal.into_callable_type(db)))
|
||||
}
|
||||
Type::BoundMethod(bound_method) => {
|
||||
Some(Type::Callable(bound_method.into_callable_type(db)))
|
||||
Some(CallableTypes::one(bound_method.into_callable_type(db)))
|
||||
}
|
||||
|
||||
Type::NominalInstance(_) | Type::ProtocolInstance(_) => {
|
||||
|
|
@ -1574,13 +1578,22 @@ impl<'db> Type<'db> {
|
|||
// TODO: This is unsound so in future we can consider an opt-in option to disable it.
|
||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Class(class) => Some(class.into_callable(db)),
|
||||
SubclassOfInner::Dynamic(dynamic) => Some(CallableType::single(
|
||||
SubclassOfInner::Dynamic(dynamic) => {
|
||||
Some(CallableTypes::one(CallableType::single(
|
||||
db,
|
||||
Signature::new(Parameters::unknown(), Some(Type::Dynamic(dynamic))),
|
||||
)),
|
||||
)))
|
||||
}
|
||||
},
|
||||
|
||||
Type::Union(union) => union.try_map(db, |element| element.try_upcast_to_callable(db)),
|
||||
Type::Union(union) => {
|
||||
let mut callables = SmallVec::new();
|
||||
for element in union.elements(db) {
|
||||
let element_callable = element.try_upcast_to_callable(db)?;
|
||||
callables.extend(element_callable.into_inner());
|
||||
}
|
||||
Some(CallableTypes(callables))
|
||||
}
|
||||
|
||||
Type::EnumLiteral(enum_literal) => enum_literal
|
||||
.enum_class_instance(db)
|
||||
|
|
@ -1588,26 +1601,30 @@ impl<'db> Type<'db> {
|
|||
|
||||
Type::TypeAlias(alias) => alias.value_type(db).try_upcast_to_callable(db),
|
||||
|
||||
Type::KnownBoundMethod(method) => Some(Type::Callable(CallableType::new(
|
||||
Type::KnownBoundMethod(method) => Some(CallableTypes::one(CallableType::new(
|
||||
db,
|
||||
CallableSignature::from_overloads(method.signatures(db)),
|
||||
false,
|
||||
))),
|
||||
|
||||
Type::WrapperDescriptor(wrapper_descriptor) => Some(Type::Callable(CallableType::new(
|
||||
Type::WrapperDescriptor(wrapper_descriptor) => {
|
||||
Some(CallableTypes::one(CallableType::new(
|
||||
db,
|
||||
CallableSignature::from_overloads(wrapper_descriptor.signatures(db)),
|
||||
false,
|
||||
))),
|
||||
)))
|
||||
}
|
||||
|
||||
Type::KnownInstance(KnownInstanceType::NewType(newtype)) => Some(CallableType::single(
|
||||
Type::KnownInstance(KnownInstanceType::NewType(newtype)) => {
|
||||
Some(CallableTypes::one(CallableType::single(
|
||||
db,
|
||||
Signature::new(
|
||||
Parameters::new([Parameter::positional_only(None)
|
||||
.with_annotated_type(newtype.base(db).instance_type(db))]),
|
||||
Some(Type::NewTypeInstance(newtype)),
|
||||
),
|
||||
)),
|
||||
)))
|
||||
}
|
||||
|
||||
Type::Never
|
||||
| Type::DataclassTransformer(_)
|
||||
|
|
@ -2183,18 +2200,20 @@ impl<'db> Type<'db> {
|
|||
)
|
||||
}),
|
||||
|
||||
(_, Type::Callable(_)) => relation_visitor.visit((self, target, relation), || {
|
||||
self.try_upcast_to_callable(db).when_some_and(|callable| {
|
||||
callable.has_relation_to_impl(
|
||||
(_, Type::Callable(other_callable)) => {
|
||||
relation_visitor.visit((self, target, relation), || {
|
||||
self.try_upcast_to_callable(db).when_some_and(|callables| {
|
||||
callables.has_relation_to_impl(
|
||||
db,
|
||||
target,
|
||||
other_callable,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
(_, Type::ProtocolInstance(protocol)) => {
|
||||
relation_visitor.visit((self, target, relation), || {
|
||||
|
|
@ -4093,7 +4112,7 @@ impl<'db> Type<'db> {
|
|||
Some((self, AttributeKind::NormalOrNonDataDescriptor))
|
||||
} else {
|
||||
Some((
|
||||
Type::Callable(callable.bind_self(db)),
|
||||
Type::Callable(callable.bind_self(db, None)),
|
||||
AttributeKind::NormalOrNonDataDescriptor,
|
||||
))
|
||||
};
|
||||
|
|
@ -5627,7 +5646,7 @@ impl<'db> Type<'db> {
|
|||
.with_annotated_type(UnionType::from_elements(
|
||||
db,
|
||||
[
|
||||
CallableType::single(db, getter_signature),
|
||||
Type::single_callable(db, getter_signature),
|
||||
Type::none(db),
|
||||
],
|
||||
))
|
||||
|
|
@ -5636,7 +5655,7 @@ impl<'db> Type<'db> {
|
|||
.with_annotated_type(UnionType::from_elements(
|
||||
db,
|
||||
[
|
||||
CallableType::single(db, setter_signature),
|
||||
Type::single_callable(db, setter_signature),
|
||||
Type::none(db),
|
||||
],
|
||||
))
|
||||
|
|
@ -5645,7 +5664,7 @@ impl<'db> Type<'db> {
|
|||
.with_annotated_type(UnionType::from_elements(
|
||||
db,
|
||||
[
|
||||
CallableType::single(db, deleter_signature),
|
||||
Type::single_callable(db, deleter_signature),
|
||||
Type::none(db),
|
||||
],
|
||||
))
|
||||
|
|
@ -7139,6 +7158,15 @@ impl<'db> Type<'db> {
|
|||
tcx: TypeContext<'db>,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Type<'db> {
|
||||
// If we are binding `typing.Self`, and this type is what we are binding `Self` to, return
|
||||
// early. This is not just an optimization, it also prevents us from infinitely expanding
|
||||
// the type, if it's something that can contain a `Self` reference.
|
||||
if let TypeMapping::BindSelf(self_type) = type_mapping
|
||||
&& self == *self_type
|
||||
{
|
||||
return self;
|
||||
}
|
||||
|
||||
match self {
|
||||
Type::TypeVar(bound_typevar) => match type_mapping {
|
||||
TypeMapping::Specialization(specialization) => {
|
||||
|
|
@ -10957,34 +10985,44 @@ pub(super) fn walk_callable_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
|||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for CallableType<'_> {}
|
||||
|
||||
impl<'db> CallableType<'db> {
|
||||
impl<'db> Type<'db> {
|
||||
/// Create a callable type with a single non-overloaded signature.
|
||||
pub(crate) fn single(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> {
|
||||
Type::Callable(CallableType::new(
|
||||
db,
|
||||
CallableSignature::single(signature),
|
||||
false,
|
||||
))
|
||||
pub(crate) fn single_callable(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> {
|
||||
Type::Callable(CallableType::single(db, signature))
|
||||
}
|
||||
|
||||
/// Create a non-overloaded, function-like callable type with a single signature.
|
||||
///
|
||||
/// A function-like callable will bind `self` when accessed as an attribute on an instance.
|
||||
pub(crate) fn function_like(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> {
|
||||
Type::Callable(CallableType::new(
|
||||
db,
|
||||
CallableSignature::single(signature),
|
||||
true,
|
||||
))
|
||||
pub(crate) fn function_like_callable(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> {
|
||||
Type::Callable(CallableType::function_like(db, signature))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> CallableType<'db> {
|
||||
pub(crate) fn single(db: &'db dyn Db, signature: Signature<'db>) -> CallableType<'db> {
|
||||
CallableType::new(db, CallableSignature::single(signature), false)
|
||||
}
|
||||
|
||||
pub(crate) fn function_like(db: &'db dyn Db, signature: Signature<'db>) -> CallableType<'db> {
|
||||
CallableType::new(db, CallableSignature::single(signature), true)
|
||||
}
|
||||
|
||||
/// Create a callable type which accepts any parameters and returns an `Unknown` type.
|
||||
pub(crate) fn unknown(db: &'db dyn Db) -> Type<'db> {
|
||||
Self::single(db, Signature::unknown())
|
||||
Type::Callable(Self::single(db, Signature::unknown()))
|
||||
}
|
||||
|
||||
pub(crate) fn bind_self(self, db: &'db dyn Db) -> CallableType<'db> {
|
||||
CallableType::new(db, self.signatures(db).bind_self(db, None), false)
|
||||
pub(crate) fn bind_self(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
self_type: Option<Type<'db>>,
|
||||
) -> CallableType<'db> {
|
||||
CallableType::new(db, self.signatures(db).bind_self(db, self_type), false)
|
||||
}
|
||||
|
||||
pub(crate) fn apply_self(self, db: &'db dyn Db, self_type: Type<'db>) -> CallableType<'db> {
|
||||
CallableType::new(db, self.signatures(db).apply_self(db, self_type), false)
|
||||
}
|
||||
|
||||
/// Create a callable type which represents a fully-static "bottom" callable.
|
||||
|
|
@ -11078,6 +11116,72 @@ impl<'db> CallableType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Converting a type "into a callable" can possibly return a _union_ of callables. Eventually,
|
||||
/// when coercing that result to a single type, you'll get a `UnionType`. But this lets you handle
|
||||
/// that result as a list of `CallableType`s before merging them into a `UnionType` should that be
|
||||
/// helpful.
|
||||
///
|
||||
/// Note that this type is guaranteed to contain at least one callable. If you need to support "no
|
||||
/// callables" as a possibility, use `Option<CallableTypes>`.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, get_size2::GetSize, salsa::Update)]
|
||||
pub(crate) struct CallableTypes<'db>(SmallVec<[CallableType<'db>; 1]>);
|
||||
|
||||
impl<'db> CallableTypes<'db> {
|
||||
pub(crate) fn one(callable: CallableType<'db>) -> Self {
|
||||
CallableTypes(smallvec![callable])
|
||||
}
|
||||
|
||||
pub(crate) fn from_elements(callables: impl IntoIterator<Item = CallableType<'db>>) -> Self {
|
||||
let callables: SmallVec<_> = callables.into_iter().collect();
|
||||
assert!(!callables.is_empty(), "CallableTypes should not be empty");
|
||||
CallableTypes(callables)
|
||||
}
|
||||
|
||||
pub(crate) fn exactly_one(self) -> Option<CallableType<'db>> {
|
||||
match self.0.as_slice() {
|
||||
[single] => Some(*single),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_inner(self) -> SmallVec<[CallableType<'db>; 1]> {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub(crate) fn into_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self.0.as_slice() {
|
||||
[] => unreachable!("CallableTypes should not be empty"),
|
||||
[single] => Type::Callable(*single),
|
||||
slice => UnionType::from_elements(db, slice.iter().copied().map(Type::Callable)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map(self, mut f: impl FnMut(CallableType<'db>) -> CallableType<'db>) -> Self {
|
||||
Self::from_elements(self.0.iter().map(|element| f(*element)))
|
||||
}
|
||||
|
||||
pub(crate) fn has_relation_to_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
other: CallableType<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
self.0.iter().when_all(db, |element| {
|
||||
element.has_relation_to_impl(
|
||||
db,
|
||||
other,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a specific instance of a bound method type for a builtin class.
|
||||
///
|
||||
/// Unlike bound methods of user-defined classes, these are not generally instances
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ use crate::types::tuple::{TupleSpec, TupleType};
|
|||
use crate::types::typed_dict::typed_dict_params_from_class_def;
|
||||
use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard};
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, Binding, BoundSuperType, CallableType, DATACLASS_FLAGS,
|
||||
ApplyTypeMappingVisitor, Binding, BoundSuperType, CallableType, CallableTypes, DATACLASS_FLAGS,
|
||||
DataclassFlags, DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor,
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownInstanceType,
|
||||
ManualPEP695TypeAliasType, MaterializationKind, NormalizedVisitor, PropertyInstanceType,
|
||||
|
|
@ -791,7 +791,7 @@ impl<'db> ClassType<'db> {
|
|||
.with_annotated_type(Type::instance(db, self))]);
|
||||
|
||||
let synthesized_dunder_method =
|
||||
CallableType::function_like(db, Signature::new(parameters, Some(return_type)));
|
||||
Type::function_like_callable(db, Signature::new(parameters, Some(return_type)));
|
||||
|
||||
Member::definitely_declared(synthesized_dunder_method)
|
||||
}
|
||||
|
|
@ -1013,7 +1013,7 @@ impl<'db> ClassType<'db> {
|
|||
iterable_parameter,
|
||||
]);
|
||||
|
||||
let synthesized_dunder = CallableType::function_like(
|
||||
let synthesized_dunder = Type::function_like_callable(
|
||||
db,
|
||||
Signature::new_generic(inherited_generic_context, parameters, None),
|
||||
);
|
||||
|
|
@ -1052,7 +1052,7 @@ impl<'db> ClassType<'db> {
|
|||
/// Return a callable type (or union of callable types) that represents the callable
|
||||
/// constructor signature of this class.
|
||||
#[salsa::tracked(cycle_initial=into_callable_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub(super) fn into_callable(self, db: &'db dyn Db) -> Type<'db> {
|
||||
pub(super) fn into_callable(self, db: &'db dyn Db) -> CallableTypes<'db> {
|
||||
let self_ty = Type::from(self);
|
||||
let metaclass_dunder_call_function_symbol = self_ty
|
||||
.member_lookup_with_policy(
|
||||
|
|
@ -1070,7 +1070,7 @@ impl<'db> ClassType<'db> {
|
|||
// https://typing.python.org/en/latest/spec/constructors.html#converting-a-constructor-to-callable
|
||||
// by always respecting the signature of the metaclass `__call__`, rather than
|
||||
// using a heuristic which makes unwarranted assumptions to sometimes ignore it.
|
||||
return Type::Callable(metaclass_dunder_call_function.into_callable_type(db));
|
||||
return CallableTypes::one(metaclass_dunder_call_function.into_callable_type(db));
|
||||
}
|
||||
|
||||
let dunder_new_function_symbol = self_ty.lookup_dunder_new(db);
|
||||
|
|
@ -1098,14 +1098,14 @@ impl<'db> ClassType<'db> {
|
|||
});
|
||||
|
||||
let instance_ty = Type::instance(db, self);
|
||||
let dunder_new_bound_method = Type::Callable(CallableType::new(
|
||||
let dunder_new_bound_method = CallableType::new(
|
||||
db,
|
||||
dunder_new_signature.bind_self(db, Some(instance_ty)),
|
||||
true,
|
||||
));
|
||||
);
|
||||
|
||||
if returns_non_subclass {
|
||||
return dunder_new_bound_method;
|
||||
return CallableTypes::one(dunder_new_bound_method);
|
||||
}
|
||||
Some(dunder_new_bound_method)
|
||||
} else {
|
||||
|
|
@ -1148,11 +1148,11 @@ impl<'db> ClassType<'db> {
|
|||
signature.overloads.iter().map(synthesized_signature),
|
||||
);
|
||||
|
||||
Some(Type::Callable(CallableType::new(
|
||||
Some(CallableType::new(
|
||||
db,
|
||||
synthesized_dunder_init_signature,
|
||||
true,
|
||||
)))
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -1162,12 +1162,14 @@ impl<'db> ClassType<'db> {
|
|||
|
||||
match (dunder_new_function, synthesized_dunder_init_callable) {
|
||||
(Some(dunder_new_function), Some(synthesized_dunder_init_callable)) => {
|
||||
UnionType::from_elements(
|
||||
db,
|
||||
vec![dunder_new_function, synthesized_dunder_init_callable],
|
||||
)
|
||||
CallableTypes::from_elements([
|
||||
dunder_new_function,
|
||||
synthesized_dunder_init_callable,
|
||||
])
|
||||
}
|
||||
(Some(constructor), None) | (None, Some(constructor)) => {
|
||||
CallableTypes::one(constructor)
|
||||
}
|
||||
(Some(constructor), None) | (None, Some(constructor)) => constructor,
|
||||
(None, None) => {
|
||||
// If no `__new__` or `__init__` method is found, then we fall back to looking for
|
||||
// an `object.__new__` method.
|
||||
|
|
@ -1182,17 +1184,17 @@ impl<'db> ClassType<'db> {
|
|||
if let Place::Defined(Type::FunctionLiteral(new_function), _, _) =
|
||||
new_function_symbol
|
||||
{
|
||||
Type::Callable(
|
||||
CallableTypes::one(
|
||||
new_function
|
||||
.into_bound_method_type(db, correct_return_type)
|
||||
.into_callable_type(db),
|
||||
)
|
||||
} else {
|
||||
// Fallback if no `object.__new__` is found.
|
||||
CallableType::single(
|
||||
CallableTypes::one(CallableType::single(
|
||||
db,
|
||||
Signature::new(Parameters::empty(), Some(correct_return_type)),
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1208,11 +1210,11 @@ impl<'db> ClassType<'db> {
|
|||
}
|
||||
|
||||
fn into_callable_cycle_initial<'db>(
|
||||
_db: &'db dyn Db,
|
||||
db: &'db dyn Db,
|
||||
_id: salsa::Id,
|
||||
_self: ClassType<'db>,
|
||||
) -> Type<'db> {
|
||||
Type::Never
|
||||
) -> CallableTypes<'db> {
|
||||
CallableTypes::one(CallableType::bottom(db))
|
||||
}
|
||||
|
||||
impl<'db> From<GenericAlias<'db>> for ClassType<'db> {
|
||||
|
|
@ -2156,7 +2158,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
Parameters::new([Parameter::positional_only(Some(Name::new_static("self")))]),
|
||||
Some(field.declared_ty),
|
||||
);
|
||||
let property_getter = CallableType::single(db, property_getter_signature);
|
||||
let property_getter = Type::single_callable(db, property_getter_signature);
|
||||
let property = PropertyInstanceType::new(db, Some(property_getter), None);
|
||||
return Member::definitely_declared(Type::PropertyInstance(property));
|
||||
}
|
||||
|
|
@ -2370,7 +2372,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
),
|
||||
_ => Signature::new(Parameters::new(parameters), return_ty),
|
||||
};
|
||||
Some(CallableType::function_like(db, signature))
|
||||
Some(Type::function_like_callable(db, signature))
|
||||
};
|
||||
|
||||
match (field_policy, name) {
|
||||
|
|
@ -2406,7 +2408,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
Some(KnownClass::Bool.to_instance(db)),
|
||||
);
|
||||
|
||||
Some(CallableType::function_like(db, signature))
|
||||
Some(Type::function_like_callable(db, signature))
|
||||
}
|
||||
(CodeGeneratorKind::DataclassLike(_), "__hash__") => {
|
||||
let unsafe_hash = has_dataclass_param(DataclassFlags::UNSAFE_HASH);
|
||||
|
|
@ -2422,7 +2424,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
Some(KnownClass::Int.to_instance(db)),
|
||||
);
|
||||
|
||||
Some(CallableType::function_like(db, signature))
|
||||
Some(Type::function_like_callable(db, signature))
|
||||
} else if eq && !frozen {
|
||||
Some(Type::none(db))
|
||||
} else {
|
||||
|
|
@ -2509,7 +2511,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
Some(Type::Never),
|
||||
);
|
||||
|
||||
return Some(CallableType::function_like(db, signature));
|
||||
return Some(Type::function_like_callable(db, signature));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
@ -2787,7 +2789,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
Some(Type::none(db)),
|
||||
);
|
||||
|
||||
Some(CallableType::function_like(db, signature))
|
||||
Some(Type::function_like_callable(db, signature))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1790,7 +1790,11 @@ impl<'db> InteriorNode<'db> {
|
|||
|
||||
/// Returns a sequent map for this BDD, which records the relationships between the constraints
|
||||
/// that appear in the BDD.
|
||||
#[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)]
|
||||
#[salsa::tracked(
|
||||
returns(ref),
|
||||
cycle_initial=sequent_map_cycle_initial,
|
||||
heap_size=ruff_memory_usage::heap_size,
|
||||
)]
|
||||
fn sequent_map(self, db: &'db dyn Db) -> SequentMap<'db> {
|
||||
let mut map = SequentMap::default();
|
||||
Node::Interior(self).for_each_constraint(db, &mut |constraint| {
|
||||
|
|
@ -2109,6 +2113,14 @@ impl<'db> InteriorNode<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
fn sequent_map_cycle_initial<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_id: salsa::Id,
|
||||
_self: InteriorNode<'db>,
|
||||
) -> SequentMap<'db> {
|
||||
SequentMap::default()
|
||||
}
|
||||
|
||||
/// An assignment of one BDD variable to either `true` or `false`. (When evaluating a BDD, we
|
||||
/// must provide an assignment for each variable present in the BDD.)
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use crate::types::generics::Specialization;
|
|||
use crate::types::signatures::Signature;
|
||||
use crate::types::{CallDunderError, UnionType};
|
||||
use crate::types::{
|
||||
ClassBase, ClassLiteral, KnownClass, KnownInstanceType, Type, TypeContext,
|
||||
CallableTypes, ClassBase, ClassLiteral, KnownClass, KnownInstanceType, Type, TypeContext,
|
||||
TypeVarBoundOrConstraints, class::CodeGeneratorKind,
|
||||
};
|
||||
use crate::{Db, DisplaySettings, HasType, NameKind, SemanticModel};
|
||||
|
|
@ -876,7 +876,10 @@ pub fn definitions_for_keyword_argument<'db>(
|
|||
|
||||
let mut resolved_definitions = Vec::new();
|
||||
|
||||
if let Some(Type::Callable(callable_type)) = func_type.try_upcast_to_callable(db) {
|
||||
if let Some(callable_type) = func_type
|
||||
.try_upcast_to_callable(db)
|
||||
.and_then(CallableTypes::exactly_one)
|
||||
{
|
||||
let signatures = callable_type.signatures(db);
|
||||
|
||||
// For each signature, find the parameter with the matching name
|
||||
|
|
@ -987,7 +990,10 @@ pub fn call_signature_details<'db>(
|
|||
let func_type = call_expr.func.inferred_type(model);
|
||||
|
||||
// Use into_callable to handle all the complex type conversions
|
||||
if let Some(callable_type) = func_type.try_upcast_to_callable(db) {
|
||||
if let Some(callable_type) = func_type
|
||||
.try_upcast_to_callable(db)
|
||||
.map(|callables| callables.into_type(db))
|
||||
{
|
||||
let call_arguments =
|
||||
CallArguments::from_arguments(&call_expr.arguments, |_, splatted_value| {
|
||||
splatted_value.inferred_type(model)
|
||||
|
|
@ -1042,7 +1048,7 @@ pub fn call_type_simplified_by_overloads<'db>(
|
|||
let func_type = call_expr.func.inferred_type(model);
|
||||
|
||||
// Use into_callable to handle all the complex type conversions
|
||||
let callable_type = func_type.try_upcast_to_callable(db)?;
|
||||
let callable_type = func_type.try_upcast_to_callable(db)?.into_type(db);
|
||||
let bindings = callable_type.bindings(db);
|
||||
|
||||
// If the callable is trivial this analysis is useless, bail out
|
||||
|
|
|
|||
|
|
@ -101,8 +101,8 @@ use crate::types::typed_dict::{
|
|||
};
|
||||
use crate::types::visitor::any_over_type;
|
||||
use crate::types::{
|
||||
CallDunderError, CallableBinding, CallableType, ClassLiteral, ClassType, DataclassParams,
|
||||
DynamicType, InternedType, IntersectionBuilder, IntersectionType, KnownClass,
|
||||
CallDunderError, CallableBinding, CallableType, CallableTypes, ClassLiteral, ClassType,
|
||||
DataclassParams, DynamicType, InternedType, IntersectionBuilder, IntersectionType, KnownClass,
|
||||
KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate,
|
||||
PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, SubclassOfType,
|
||||
TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext,
|
||||
|
|
@ -2291,7 +2291,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
|
||||
let is_input_function_like = inferred_ty
|
||||
.try_upcast_to_callable(self.db())
|
||||
.and_then(Type::as_callable)
|
||||
.and_then(CallableTypes::exactly_one)
|
||||
.is_some_and(|callable| callable.is_function_like(self.db()));
|
||||
|
||||
if is_input_function_like
|
||||
|
|
@ -3284,7 +3284,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
// specified in this context.
|
||||
match default {
|
||||
ast::Expr::EllipsisLiteral(_) => {
|
||||
CallableType::single(self.db(), Signature::new(Parameters::gradual_form(), None))
|
||||
Type::single_callable(self.db(), Signature::new(Parameters::gradual_form(), None))
|
||||
}
|
||||
ast::Expr::List(ast::ExprList { elts, .. }) => {
|
||||
let mut parameter_types = Vec::with_capacity(elts.len());
|
||||
|
|
@ -3312,7 +3312,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}))
|
||||
};
|
||||
|
||||
CallableType::single(self.db(), Signature::new(parameters, None))
|
||||
Type::single_callable(self.db(), Signature::new(parameters, None))
|
||||
}
|
||||
ast::Expr::Name(name) => {
|
||||
let name_ty = self.infer_name_load(name);
|
||||
|
|
@ -7945,7 +7945,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
// TODO: Useful inference of a lambda's return type will require a different approach,
|
||||
// which does the inference of the body expression based on arguments at each call site,
|
||||
// rather than eagerly computing a return type without knowing the argument types.
|
||||
CallableType::function_like(self.db(), Signature::new(parameters, Some(Type::unknown())))
|
||||
Type::function_like_callable(self.db(), Signature::new(parameters, Some(Type::unknown())))
|
||||
}
|
||||
|
||||
fn infer_call_expression(
|
||||
|
|
|
|||
|
|
@ -1011,7 +1011,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
let callable_type = if let (Some(parameters), Some(return_type), true) =
|
||||
(parameters, return_type, correct_argument_number)
|
||||
{
|
||||
CallableType::single(db, Signature::new(parameters, Some(return_type)))
|
||||
Type::single_callable(db, Signature::new(parameters, Some(return_type)))
|
||||
} else {
|
||||
CallableType::unknown(db)
|
||||
};
|
||||
|
|
@ -1227,7 +1227,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
|
||||
let argument_type = self.infer_expression(&arguments[0], TypeContext::default());
|
||||
|
||||
let Some(callable_type) = argument_type.try_upcast_to_callable(db) else {
|
||||
let Some(callable_type) = argument_type
|
||||
.try_upcast_to_callable(db)
|
||||
.map(|callables| callables.into_type(self.db()))
|
||||
else {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_TYPE_FORM, arguments_slice)
|
||||
|
|
|
|||
|
|
@ -126,7 +126,10 @@ fn check_class_declaration<'db>(
|
|||
break;
|
||||
};
|
||||
|
||||
let Some(superclass_type_as_callable) = superclass_type.try_upcast_to_callable(db) else {
|
||||
let Some(superclass_type_as_callable) = superclass_type
|
||||
.try_upcast_to_callable(db)
|
||||
.map(|callables| callables.into_type(db))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use crate::place::{builtins_symbol, known_module_symbol};
|
|||
use crate::types::enums::is_single_member_enum;
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{
|
||||
BoundMethodType, CallableType, EnumLiteralType, IntersectionBuilder, KnownClass, Parameter,
|
||||
Parameters, Signature, SpecialFormType, SubclassOfType, Type, UnionType,
|
||||
BoundMethodType, EnumLiteralType, IntersectionBuilder, KnownClass, Parameter, Parameters,
|
||||
Signature, SpecialFormType, SubclassOfType, Type, UnionType,
|
||||
};
|
||||
use crate::{Db, module_resolver::KnownModule};
|
||||
use quickcheck::{Arbitrary, Gen};
|
||||
|
|
@ -229,7 +229,7 @@ impl Ty {
|
|||
|
||||
create_bound_method(db, function, builtins_class)
|
||||
}
|
||||
Ty::Callable { params, returns } => CallableType::single(
|
||||
Ty::Callable { params, returns } => Type::single_callable(
|
||||
db,
|
||||
Signature::new(
|
||||
params.into_parameters(db),
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ impl<'db> ProtocolInterface<'db> {
|
|||
Parameters::new([Parameter::positional_only(Some(Name::new_static("self")))]),
|
||||
Some(ty.normalized(db)),
|
||||
);
|
||||
let property_getter = CallableType::single(db, property_getter_signature);
|
||||
let property_getter = Type::single_callable(db, property_getter_signature);
|
||||
let property = PropertyInstanceType::new(db, Some(property_getter), None);
|
||||
(
|
||||
Name::new(name),
|
||||
|
|
@ -300,7 +300,7 @@ impl<'db> ProtocolInterface<'db> {
|
|||
.and(db, || {
|
||||
our_type.has_relation_to_impl(
|
||||
db,
|
||||
Type::Callable(other_type.bind_self(db)),
|
||||
Type::Callable(other_type.bind_self(db, None)),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
|
|
@ -311,9 +311,9 @@ impl<'db> ProtocolInterface<'db> {
|
|||
(
|
||||
ProtocolMemberKind::Method(our_method),
|
||||
ProtocolMemberKind::Method(other_method),
|
||||
) => our_method.bind_self(db).has_relation_to_impl(
|
||||
) => our_method.bind_self(db, None).has_relation_to_impl(
|
||||
db,
|
||||
other_method.bind_self(db),
|
||||
other_method.bind_self(db, None),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
|
|
@ -676,10 +676,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
|||
// unfortunately not sufficient to obtain the `Callable` supertypes of these types, due to the
|
||||
// complex interaction between `__new__`, `__init__` and metaclass `__call__`.
|
||||
let attribute_type = if self.name == "__call__" {
|
||||
let Some(attribute_type) = other.try_upcast_to_callable(db) else {
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
attribute_type
|
||||
other
|
||||
} else {
|
||||
let Place::Defined(attribute_type, _, Definedness::AlwaysDefined) = other
|
||||
.invoke_descriptor_protocol(
|
||||
|
|
@ -696,14 +693,32 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
|||
attribute_type
|
||||
};
|
||||
|
||||
attribute_type.has_relation_to_impl(
|
||||
// TODO: Instances of `typing.Self` in the protocol member should specialize to the
|
||||
// type that we are checking. Without this, we will treat `Self` as an inferable
|
||||
// typevar, and allow it to match against _any_ type.
|
||||
//
|
||||
// It's not very principled, but we also use the literal fallback type, instead of
|
||||
// `other` directly. This lets us check whether things like `Literal[0]` satisfy a
|
||||
// protocol that includes methods that have `typing.Self` annotations, without
|
||||
// overly constraining `Self` to that specific literal.
|
||||
//
|
||||
// With the new solver, we should be to replace all of this with an additional
|
||||
// constraint that enforces what `Self` can specialize to.
|
||||
let fallback_other = other.literal_fallback_instance(db).unwrap_or(other);
|
||||
attribute_type
|
||||
.try_upcast_to_callable(db)
|
||||
.when_some_and(|callables| {
|
||||
callables
|
||||
.map(|callable| callable.apply_self(db, fallback_other))
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
Type::Callable(method.bind_self(db)),
|
||||
method.bind_self(db, Some(fallback_other)),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
// TODO: consider the types of the attribute on `other` for property members
|
||||
ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!(
|
||||
|
|
|
|||
|
|
@ -213,6 +213,19 @@ impl<'db> CallableSignature<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Replaces any occurrences of `typing.Self` in the parameter and return annotations with the
|
||||
/// given type. (Does not bind the `self` parameter; to do that, use
|
||||
/// [`bind_self`][Self::bind_self].)
|
||||
pub(crate) fn apply_self(&self, db: &'db dyn Db, self_type: Type<'db>) -> Self {
|
||||
Self {
|
||||
overloads: self
|
||||
.overloads
|
||||
.iter()
|
||||
.map(|signature| signature.apply_self(db, self_type))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_subtype_of_impl(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
|
|
@ -628,6 +641,28 @@ impl<'db> Signature<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_self(&self, db: &'db dyn Db, self_type: Type<'db>) -> Self {
|
||||
let parameters = self.parameters.apply_type_mapping_impl(
|
||||
db,
|
||||
&TypeMapping::BindSelf(self_type),
|
||||
TypeContext::default(),
|
||||
&ApplyTypeMappingVisitor::default(),
|
||||
);
|
||||
let return_ty = self.return_ty.map(|ty| {
|
||||
ty.apply_type_mapping(
|
||||
db,
|
||||
&TypeMapping::BindSelf(self_type),
|
||||
TypeContext::default(),
|
||||
)
|
||||
});
|
||||
Self {
|
||||
generic_context: self.generic_context,
|
||||
definition: self.definition,
|
||||
parameters,
|
||||
return_ty,
|
||||
}
|
||||
}
|
||||
|
||||
fn inferable_typevars(&self, db: &'db dyn Db) -> InferableTypeVars<'db, 'db> {
|
||||
match self.generic_context {
|
||||
Some(generic_context) => generic_context.inferable_typevars(db),
|
||||
|
|
|
|||
Loading…
Reference in New Issue