Skip to content
Merged
3 changes: 1 addition & 2 deletions crates/ty_python_semantic/resources/mdtest/protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -2112,8 +2112,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))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are other Self-related tests in this file that are still TODO, since they also require being able to check against generic protocol members correctly.


# TODO: these should pass
static_assert(not is_assignable_to(NominalNotGeneric, NewStyleFunctionScoped)) # error: [static-assert-error]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,7 @@ impl ReachabilityConstraints {
}

let overloads_iterator =
if let Some(Type::Callable(callable)) = ty.try_upcast_to_callable(db) {
if let Some(callable) = ty.try_upcast_to_callable(db).exactly_one() {
callable.signatures(db).overloads.iter()
} else {
return Truthiness::AlwaysFalse.negate_if(!predicate.is_positive);
Expand Down
199 changes: 153 additions & 46 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -1524,17 +1525,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) -> CallableTypes<'db> {
Comment thread
AlexWaygood marked this conversation as resolved.
Outdated
match self {
Type::Callable(_) => Some(self),
Type::Callable(callable) => CallableTypes::one(callable),

Type::Dynamic(_) => Some(CallableType::function_like(db, Signature::dynamic(self))),
Type::Dynamic(_) => CallableTypes::one(CallableType::function_like_callable(
db,
Signature::dynamic(self),
)),

Type::FunctionLiteral(function_literal) => {
Some(Type::Callable(function_literal.into_callable_type(db)))
CallableTypes::one(function_literal.into_callable_type(db))
}
Type::BoundMethod(bound_method) => {
Some(Type::Callable(bound_method.into_callable_type(db)))
CallableTypes::one(bound_method.into_callable_type(db))
}

Type::NominalInstance(_) | Type::ProtocolInstance(_) => {
Expand All @@ -1549,57 +1553,77 @@ impl<'db> Type<'db> {
if let Place::Defined(ty, _, Definedness::AlwaysDefined) = call_symbol {
ty.try_upcast_to_callable(db)
} else {
None
CallableTypes::none()
}
}
Type::ClassLiteral(class_literal) => {
Some(ClassType::NonGeneric(class_literal).into_callable(db))
ClassType::NonGeneric(class_literal).into_callable(db)
}

Type::GenericAlias(alias) => Some(ClassType::Generic(alias).into_callable(db)),
Type::GenericAlias(alias) => ClassType::Generic(alias).into_callable(db),

Type::NewTypeInstance(newtype) => {
Type::instance(db, newtype.base_class_type(db)).try_upcast_to_callable(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(
db,
Signature::new(Parameters::unknown(), Some(Type::Dynamic(dynamic))),
)),
SubclassOfInner::Class(class) => class.into_callable(db),
SubclassOfInner::Dynamic(dynamic) => {
CallableTypes::one(CallableType::single_callable(
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 any_not_callable = false;
let callables: SmallVec<_> = union
.elements(db)
.iter()
.flat_map(|element| {
let element_callable = element.try_upcast_to_callable(db);
any_not_callable |= element_callable.is_empty();
element_callable.into_inner().into_iter()
})
.collect();
if any_not_callable {
CallableTypes::none()
} else {
CallableTypes(callables)
}
}

Type::EnumLiteral(enum_literal) => enum_literal
.enum_class_instance(db)
.try_upcast_to_callable(db),

Type::TypeAlias(alias) => alias.value_type(db).try_upcast_to_callable(db),

Type::KnownBoundMethod(method) => Some(Type::Callable(CallableType::new(
Type::KnownBoundMethod(method) => 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) => CallableTypes::one(CallableType::new(
db,
CallableSignature::from_overloads(wrapper_descriptor.signatures(db)),
false,
))),

Type::KnownInstance(KnownInstanceType::NewType(newtype)) => Some(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::KnownInstance(KnownInstanceType::NewType(newtype)) => {
CallableTypes::one(CallableType::single_callable(
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(_)
| Type::AlwaysTruthy
Expand All @@ -1610,7 +1634,7 @@ impl<'db> Type<'db> {
| Type::LiteralString
| Type::BytesLiteral(_)
| Type::TypeIs(_)
| Type::TypedDict(_) => None,
| Type::TypedDict(_) => CallableTypes::none(),

// TODO
Type::DataclassDecorator(_)
Expand All @@ -1620,7 +1644,7 @@ impl<'db> Type<'db> {
| Type::PropertyInstance(_)
| Type::Intersection(_)
| Type::TypeVar(_)
| Type::BoundSuper(_) => None,
| Type::BoundSuper(_) => CallableTypes::none(),
}
}

Expand Down Expand Up @@ -2174,18 +2198,18 @@ 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).has_relation_to_impl(
db,
target,
other_callable,
inferable,
relation,
relation_visitor,
disjointness_visitor,
)
})
}),
}

(_, Type::ProtocolInstance(protocol)) => {
relation_visitor.visit((self, target, relation), || {
Expand Down Expand Up @@ -4083,7 +4107,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,
))
};
Expand Down Expand Up @@ -10950,31 +10974,42 @@ impl get_size2::GetSize for CallableType<'_> {}
impl<'db> CallableType<'db> {
/// Create a callable type with a single non-overloaded signature.
pub(crate) fn single(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> {
Comment thread
AlexWaygood marked this conversation as resolved.
Outdated
Type::Callable(CallableType::new(
db,
CallableSignature::single(signature),
false,
))
Type::Callable(Self::single_callable(db, signature))
}

pub(crate) fn single_callable(db: &'db dyn Db, signature: Signature<'db>) -> CallableType<'db> {
CallableType::new(db, CallableSignature::single(signature), false)
}

/// 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,
))
Type::Callable(Self::function_like_callable(db, signature))
}

pub(crate) fn function_like_callable(
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())
}

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.
Expand Down Expand Up @@ -11068,6 +11103,78 @@ 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.
#[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 none() -> Self {
CallableTypes(smallvec![])
}

pub(crate) fn one(callable: CallableType<'db>) -> Self {
CallableTypes(smallvec![callable])
}

pub(crate) fn from_elements(callables: impl IntoIterator<Item = CallableType<'db>>) -> Self {
CallableTypes(callables.into_iter().collect())
}

pub(crate) fn exactly_one(self) -> Option<CallableType<'db>> {
self.0.into_iter().exactly_one().ok()
}
Comment thread
dcreager marked this conversation as resolved.

fn is_empty(&self) -> bool {
self.0.is_empty()
}

fn into_inner(self) -> SmallVec<[CallableType<'db>; 1]> {
self.0
}

pub(crate) fn into_type(self, db: &'db dyn Db) -> Option<Type<'db>> {
match self.0.as_slice() {
[] => None,
[single] => Some(Type::Callable(*single)),
slice => Some(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> {
if self.is_empty() {
return ConstraintSet::from(false);
}
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
Expand Down
Loading
Loading