Remove nested PossiblyUndefined variants

This commit is contained in:
Micha Reiser
2025-02-12 18:46:04 +01:00
parent 44eaf112f2
commit 20cfc116db
3 changed files with 51 additions and 44 deletions

View File

@@ -1907,9 +1907,6 @@ impl<'db> Type<'db> {
match self.call_dunder(db, "__len__", &CallArguments::positional([*self])) {
CallDunderOutcome::MethodNotAvailable => CallDunderLenOutcome::MethodNotAvailable,
CallDunderOutcome::Call(outcome) => CallDunderLenOutcome::Call(outcome),
CallDunderOutcome::PossiblyUnbound(outcome) => {
CallDunderLenOutcome::PossiblyUnbound(outcome)
}
}
}
@@ -1918,9 +1915,8 @@ impl<'db> Type<'db> {
///
/// In the second case, the return type of `len()` in `typeshed` (`int`)
/// is used as a fallback.
#[must_use]
fn len(&self, db: &'db dyn Db) -> Option<Type<'db>> {
let len = self.__len__(db).return_type(db)?;
fn non_negative_int_literal<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Type<'db>> {
match ty {
Type::IntLiteral(value) => (value >= 0).then_some(ty),
@@ -1936,11 +1932,12 @@ impl<'db> Type<'db> {
}
}
let len = self.__len__(db).return_type(db)?;
non_negative_int_literal(db, len)
}
/// Return the outcome of calling an object of this type.
#[must_use]
fn call(self, db: &'db dyn Db, arguments: &CallArguments<'_, 'db>) -> CallOutcome<'db> {
match self {
Type::FunctionLiteral(function_type) => {
@@ -2068,15 +2065,18 @@ impl<'db> Type<'db> {
not_callable_ty: self,
}
}
// Turn "possibly unbound object of type `Literal['__call__']`"
// into "`X` not callable (possibly unbound `__call__` method)"
CallDunderOutcome::Call(CallOutcome::PossiblyUnboundDunderCall {
called_ty: _,
call_outcome,
}) => CallOutcome::PossiblyUnboundDunderCall {
called_ty: self,
call_outcome,
},
CallDunderOutcome::Call(outcome) => outcome,
CallDunderOutcome::PossiblyUnbound(call_outcome) => {
// Turn "possibly unbound object of type `Literal['__call__']`"
// into "`X` not callable (possibly unbound `__call__` method)"
CallOutcome::PossiblyUnboundDunderCall {
called_ty: self,
call_outcome: Box::new(call_outcome),
}
}
CallDunderOutcome::MethodNotAvailable => {
// Turn "`X.__call__` unbound" into "`X` not callable"
CallOutcome::NotCallable {
@@ -2111,7 +2111,6 @@ impl<'db> Type<'db> {
/// `receiver_ty` must be `Type::Instance(_)` or `Type::ClassLiteral`.
///
/// TODO: handle `super()` objects properly
#[must_use]
fn call_bound(
self,
db: &'db dyn Db,
@@ -2163,7 +2162,10 @@ impl<'db> Type<'db> {
CallDunderOutcome::Call(callable_ty.call(db, arguments))
}
Symbol::Type(callable_ty, Boundness::PossiblyUnbound) => {
CallDunderOutcome::PossiblyUnbound(callable_ty.call(db, arguments))
CallDunderOutcome::Call(CallOutcome::PossiblyUnboundDunderCall {
call_outcome: Box::new(callable_ty.call(db, arguments)),
called_ty: callable_ty,
})
}
Symbol::Unbound => CallDunderOutcome::MethodNotAvailable,
}
@@ -2187,8 +2189,7 @@ impl<'db> Type<'db> {
let dunder_iter_result =
self.call_dunder(db, "__iter__", &CallArguments::positional([self]));
match dunder_iter_result {
CallDunderOutcome::Call(ref call_outcome)
| CallDunderOutcome::PossiblyUnbound(ref call_outcome) => {
CallDunderOutcome::Call(ref call_outcome) => {
let Some(iterator_ty) = call_outcome.return_type(db) else {
return IterationOutcome::NotIterable {
not_iterable_ty: self,
@@ -2199,7 +2200,7 @@ impl<'db> Type<'db> {
.call_dunder(db, "__next__", &CallArguments::positional([iterator_ty]))
.return_type(db)
{
if matches!(dunder_iter_result, CallDunderOutcome::PossiblyUnbound(..)) {
if call_outcome.is_possibly_unbound() {
IterationOutcome::PossiblyUnboundDunderIter {
iterable_ty: self,
element_ty,

View File

@@ -10,6 +10,7 @@ mod bind;
pub(super) use arguments::{Argument, CallArguments};
pub(super) use bind::{bind_call, CallBinding};
#[must_use]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum CallOutcome<'db> {
Callable {
@@ -50,6 +51,10 @@ impl<'db> CallOutcome<'db> {
}
}
pub(super) fn is_possibly_unbound(&self) -> bool {
matches!(self, Self::PossiblyUnboundDunderCall { .. })
}
/// Get the return type of the call, or `None` if not callable.
pub(super) fn return_type(&self, db: &'db dyn Db) -> Option<Type<'db>> {
match self {
@@ -215,24 +220,23 @@ impl<'db> CallOutcome<'db> {
}
}
#[must_use]
#[derive(Debug)]
pub(super) enum CallDunderOutcome<'db> {
Call(CallOutcome<'db>),
PossiblyUnbound(CallOutcome<'db>),
MethodNotAvailable,
}
impl<'db> CallDunderOutcome<'db> {
pub(super) fn call_outcome(&self) -> Option<&CallOutcome<'db>> {
match self {
Self::Call(outcome) | Self::PossiblyUnbound(outcome) => Some(outcome),
Self::MethodNotAvailable => None,
}
}
pub(super) fn return_type(&self, db: &'db dyn Db) -> Option<Type<'db>> {
match self {
Self::Call(outcome) => outcome.return_type(db),
Self::PossiblyUnbound { .. } => None,
Self::Call(outcome) => {
if outcome.is_possibly_unbound() {
None
} else {
outcome.return_type(db)
}
}
Self::MethodNotAvailable => None,
}
}
@@ -293,6 +297,7 @@ impl<'db> NotCallableError<'db> {
}
}
#[must_use]
#[derive(Debug)]
pub(super) enum CallDunderLenOutcome<'db> {
/// The length is statically known.
@@ -301,9 +306,6 @@ pub(super) enum CallDunderLenOutcome<'db> {
/// The length is determined by calling `__len__`.
Call(CallOutcome<'db>),
/// The length is determined by calling `__len__` but it isn't always bound.
PossiblyUnbound(CallOutcome<'db>),
/// The object doesn't have a `__len__` method and, thus, doesn't implement sized.
MethodNotAvailable,
}
@@ -315,8 +317,7 @@ impl<'db> CallDunderLenOutcome<'db> {
// TODO: Fall back to `int` if value is too large?
i64::try_from(*len).ok().map(Type::IntLiteral)
}
CallDunderLenOutcome::Call(call_outcome)
| CallDunderLenOutcome::PossiblyUnbound(call_outcome) => call_outcome.return_type(db),
CallDunderLenOutcome::Call(call_outcome) => call_outcome.return_type(db),
CallDunderLenOutcome::MethodNotAvailable => None,
}
}

View File

@@ -50,7 +50,9 @@ use crate::semantic_index::semantic_index;
use crate::semantic_index::symbol::{NodeWithScopeKind, NodeWithScopeRef, ScopeId};
use crate::semantic_index::SemanticIndex;
use crate::stdlib::builtins_module_scope;
use crate::types::call::{Argument, CallArguments, CallOutcome};
use crate::types::call::{
Argument, CallArguments, CallDunderLenOutcome, CallDunderOutcome, CallOutcome,
};
use crate::types::diagnostic::{
report_invalid_arguments_to_annotated, report_invalid_assignment,
report_invalid_attribute_assignment, report_unresolved_module, TypeCheckDiagnostics,
@@ -3346,7 +3348,13 @@ impl<'db> TypeInferenceBuilder<'db> {
}
KnownFunction::Len => {
// TODO: Assert that the argument implements the `Sized` protocol (correctly).
let Some(sized) = binding.one_parameter_type() else {
return call;
};
if let CallDunderLenOutcome::Call(_) = sized.__len__(self.db()) {
// TODO: Assert that the argument implements the `Sized` protocol (correctly).
}
}
_ => {}
}
@@ -3671,14 +3679,11 @@ impl<'db> TypeInferenceBuilder<'db> {
}
};
if let Some(call) = operand_type
.call_dunder(
self.db(),
unary_dunder_method,
&CallArguments::positional([operand_type]),
)
.call_outcome()
{
if let CallDunderOutcome::Call(call) = operand_type.call_dunder(
self.db(),
unary_dunder_method,
&CallArguments::positional([operand_type]),
) {
match call.return_type_result(&self.context, AnyNodeRef::ExprUnaryOp(unary)) {
Ok(t) => t,
Err(e) => {