mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:37 +00:00
[red-knot] split call-outcome enums to their own submodule (#14898)
## Summary This is already several hundred lines of code, and it will get more complex with call-signature checking. ## Test Plan This is a pure code move; the moved code wasn't changed, just imports. Existing tests pass.
This commit is contained in:
parent
1a3c311ac5
commit
03fb2e5ac1
2 changed files with 316 additions and 309 deletions
|
@ -2,7 +2,7 @@ use std::hash::Hash;
|
||||||
|
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ruff_db::diagnostic::{DiagnosticId, Severity};
|
use ruff_db::diagnostic::Severity;
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
|
|
||||||
|
@ -25,12 +25,14 @@ use crate::stdlib::{
|
||||||
builtins_symbol, core_module_symbol, typing_extensions_symbol, CoreStdlibModule,
|
builtins_symbol, core_module_symbol, typing_extensions_symbol, CoreStdlibModule,
|
||||||
};
|
};
|
||||||
use crate::symbol::{Boundness, Symbol};
|
use crate::symbol::{Boundness, Symbol};
|
||||||
use crate::types::diagnostic::{TypeCheckDiagnosticsBuilder, CALL_NON_CALLABLE};
|
use crate::types::call::{CallDunderResult, CallOutcome};
|
||||||
|
use crate::types::diagnostic::TypeCheckDiagnosticsBuilder;
|
||||||
use crate::types::mro::{ClassBase, Mro, MroError, MroIterator};
|
use crate::types::mro::{ClassBase, Mro, MroError, MroIterator};
|
||||||
use crate::types::narrow::narrowing_constraint;
|
use crate::types::narrow::narrowing_constraint;
|
||||||
use crate::{Db, FxOrderSet, Module, Program, PythonVersion};
|
use crate::{Db, FxOrderSet, Module, Program, PythonVersion};
|
||||||
|
|
||||||
mod builder;
|
mod builder;
|
||||||
|
mod call;
|
||||||
mod diagnostic;
|
mod diagnostic;
|
||||||
mod display;
|
mod display;
|
||||||
mod infer;
|
mod infer;
|
||||||
|
@ -2214,313 +2216,6 @@ pub enum TypeVarBoundOrConstraints<'db> {
|
||||||
Constraints(TupleType<'db>),
|
Constraints(TupleType<'db>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
enum CallOutcome<'db> {
|
|
||||||
Callable {
|
|
||||||
return_ty: Type<'db>,
|
|
||||||
},
|
|
||||||
RevealType {
|
|
||||||
return_ty: Type<'db>,
|
|
||||||
revealed_ty: Type<'db>,
|
|
||||||
},
|
|
||||||
NotCallable {
|
|
||||||
not_callable_ty: Type<'db>,
|
|
||||||
},
|
|
||||||
Union {
|
|
||||||
called_ty: Type<'db>,
|
|
||||||
outcomes: Box<[CallOutcome<'db>]>,
|
|
||||||
},
|
|
||||||
PossiblyUnboundDunderCall {
|
|
||||||
called_ty: Type<'db>,
|
|
||||||
call_outcome: Box<CallOutcome<'db>>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'db> CallOutcome<'db> {
|
|
||||||
/// Create a new `CallOutcome::Callable` with given return type.
|
|
||||||
fn callable(return_ty: Type<'db>) -> CallOutcome<'db> {
|
|
||||||
CallOutcome::Callable { return_ty }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new `CallOutcome::NotCallable` with given not-callable type.
|
|
||||||
fn not_callable(not_callable_ty: Type<'db>) -> CallOutcome<'db> {
|
|
||||||
CallOutcome::NotCallable { not_callable_ty }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new `CallOutcome::RevealType` with given revealed and return types.
|
|
||||||
fn revealed(return_ty: Type<'db>, revealed_ty: Type<'db>) -> CallOutcome<'db> {
|
|
||||||
CallOutcome::RevealType {
|
|
||||||
return_ty,
|
|
||||||
revealed_ty,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new `CallOutcome::Union` with given wrapped outcomes.
|
|
||||||
fn union(
|
|
||||||
called_ty: Type<'db>,
|
|
||||||
outcomes: impl IntoIterator<Item = CallOutcome<'db>>,
|
|
||||||
) -> CallOutcome<'db> {
|
|
||||||
CallOutcome::Union {
|
|
||||||
called_ty,
|
|
||||||
outcomes: outcomes.into_iter().collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the return type of the call, or `None` if not callable.
|
|
||||||
fn return_ty(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
|
||||||
match self {
|
|
||||||
Self::Callable { return_ty } => Some(*return_ty),
|
|
||||||
Self::RevealType {
|
|
||||||
return_ty,
|
|
||||||
revealed_ty: _,
|
|
||||||
} => Some(*return_ty),
|
|
||||||
Self::NotCallable { not_callable_ty: _ } => None,
|
|
||||||
Self::Union {
|
|
||||||
outcomes,
|
|
||||||
called_ty: _,
|
|
||||||
} => outcomes
|
|
||||||
.iter()
|
|
||||||
// If all outcomes are NotCallable, we return None; if some outcomes are callable
|
|
||||||
// and some are not, we return a union including Unknown.
|
|
||||||
.fold(None, |acc, outcome| {
|
|
||||||
let ty = outcome.return_ty(db);
|
|
||||||
match (acc, ty) {
|
|
||||||
(None, None) => None,
|
|
||||||
(None, Some(ty)) => Some(UnionBuilder::new(db).add(ty)),
|
|
||||||
(Some(builder), ty) => Some(builder.add(ty.unwrap_or(Type::Unknown))),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(UnionBuilder::build),
|
|
||||||
Self::PossiblyUnboundDunderCall { call_outcome, .. } => call_outcome.return_ty(db),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the return type of the call, emitting default diagnostics if needed.
|
|
||||||
fn unwrap_with_diagnostic<'a>(
|
|
||||||
&self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
node: ast::AnyNodeRef,
|
|
||||||
diagnostics: &'a mut TypeCheckDiagnosticsBuilder<'db>,
|
|
||||||
) -> Type<'db> {
|
|
||||||
match self.return_ty_result(db, node, diagnostics) {
|
|
||||||
Ok(return_ty) => return_ty,
|
|
||||||
Err(NotCallableError::Type {
|
|
||||||
not_callable_ty,
|
|
||||||
return_ty,
|
|
||||||
}) => {
|
|
||||||
diagnostics.add_lint(
|
|
||||||
&CALL_NON_CALLABLE,
|
|
||||||
node,
|
|
||||||
format_args!(
|
|
||||||
"Object of type `{}` is not callable",
|
|
||||||
not_callable_ty.display(db)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return_ty
|
|
||||||
}
|
|
||||||
Err(NotCallableError::UnionElement {
|
|
||||||
not_callable_ty,
|
|
||||||
called_ty,
|
|
||||||
return_ty,
|
|
||||||
}) => {
|
|
||||||
diagnostics.add_lint(
|
|
||||||
&CALL_NON_CALLABLE,
|
|
||||||
node,
|
|
||||||
format_args!(
|
|
||||||
"Object of type `{}` is not callable (due to union element `{}`)",
|
|
||||||
called_ty.display(db),
|
|
||||||
not_callable_ty.display(db),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return_ty
|
|
||||||
}
|
|
||||||
Err(NotCallableError::UnionElements {
|
|
||||||
not_callable_tys,
|
|
||||||
called_ty,
|
|
||||||
return_ty,
|
|
||||||
}) => {
|
|
||||||
diagnostics.add_lint(
|
|
||||||
&CALL_NON_CALLABLE,
|
|
||||||
node,
|
|
||||||
format_args!(
|
|
||||||
"Object of type `{}` is not callable (due to union elements {})",
|
|
||||||
called_ty.display(db),
|
|
||||||
not_callable_tys.display(db),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return_ty
|
|
||||||
}
|
|
||||||
Err(NotCallableError::PossiblyUnboundDunderCall {
|
|
||||||
callable_ty: called_ty,
|
|
||||||
return_ty,
|
|
||||||
}) => {
|
|
||||||
diagnostics.add_lint(
|
|
||||||
&CALL_NON_CALLABLE,
|
|
||||||
node,
|
|
||||||
format_args!(
|
|
||||||
"Object of type `{}` is not callable (possibly unbound `__call__` method)",
|
|
||||||
called_ty.display(db)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return_ty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the return type of the call as a result.
|
|
||||||
fn return_ty_result<'a>(
|
|
||||||
&self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
node: ast::AnyNodeRef,
|
|
||||||
diagnostics: &'a mut TypeCheckDiagnosticsBuilder<'db>,
|
|
||||||
) -> Result<Type<'db>, NotCallableError<'db>> {
|
|
||||||
match self {
|
|
||||||
Self::Callable { return_ty } => Ok(*return_ty),
|
|
||||||
Self::RevealType {
|
|
||||||
return_ty,
|
|
||||||
revealed_ty,
|
|
||||||
} => {
|
|
||||||
diagnostics.add(
|
|
||||||
node,
|
|
||||||
DiagnosticId::RevealedType,
|
|
||||||
Severity::Info,
|
|
||||||
format_args!("Revealed type is `{}`", revealed_ty.display(db)),
|
|
||||||
);
|
|
||||||
Ok(*return_ty)
|
|
||||||
}
|
|
||||||
Self::NotCallable { not_callable_ty } => Err(NotCallableError::Type {
|
|
||||||
not_callable_ty: *not_callable_ty,
|
|
||||||
return_ty: Type::Unknown,
|
|
||||||
}),
|
|
||||||
Self::PossiblyUnboundDunderCall {
|
|
||||||
called_ty,
|
|
||||||
call_outcome,
|
|
||||||
} => Err(NotCallableError::PossiblyUnboundDunderCall {
|
|
||||||
callable_ty: *called_ty,
|
|
||||||
return_ty: call_outcome.return_ty(db).unwrap_or(Type::Unknown),
|
|
||||||
}),
|
|
||||||
Self::Union {
|
|
||||||
outcomes,
|
|
||||||
called_ty,
|
|
||||||
} => {
|
|
||||||
let mut not_callable = vec![];
|
|
||||||
let mut union_builder = UnionBuilder::new(db);
|
|
||||||
let mut revealed = false;
|
|
||||||
for outcome in outcomes {
|
|
||||||
let return_ty = match outcome {
|
|
||||||
Self::NotCallable { not_callable_ty } => {
|
|
||||||
not_callable.push(*not_callable_ty);
|
|
||||||
Type::Unknown
|
|
||||||
}
|
|
||||||
Self::RevealType {
|
|
||||||
return_ty,
|
|
||||||
revealed_ty: _,
|
|
||||||
} => {
|
|
||||||
if revealed {
|
|
||||||
*return_ty
|
|
||||||
} else {
|
|
||||||
revealed = true;
|
|
||||||
outcome.unwrap_with_diagnostic(db, node, diagnostics)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => outcome.unwrap_with_diagnostic(db, node, diagnostics),
|
|
||||||
};
|
|
||||||
union_builder = union_builder.add(return_ty);
|
|
||||||
}
|
|
||||||
let return_ty = union_builder.build();
|
|
||||||
match not_callable[..] {
|
|
||||||
[] => Ok(return_ty),
|
|
||||||
[elem] => Err(NotCallableError::UnionElement {
|
|
||||||
not_callable_ty: elem,
|
|
||||||
called_ty: *called_ty,
|
|
||||||
return_ty,
|
|
||||||
}),
|
|
||||||
_ if not_callable.len() == outcomes.len() => Err(NotCallableError::Type {
|
|
||||||
not_callable_ty: *called_ty,
|
|
||||||
return_ty,
|
|
||||||
}),
|
|
||||||
_ => Err(NotCallableError::UnionElements {
|
|
||||||
not_callable_tys: not_callable.into_boxed_slice(),
|
|
||||||
called_ty: *called_ty,
|
|
||||||
return_ty,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CallDunderResult<'db> {
|
|
||||||
CallOutcome(CallOutcome<'db>),
|
|
||||||
PossiblyUnbound(CallOutcome<'db>),
|
|
||||||
MethodNotAvailable,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'db> CallDunderResult<'db> {
|
|
||||||
fn return_ty(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
|
||||||
match self {
|
|
||||||
Self::CallOutcome(outcome) => outcome.return_ty(db),
|
|
||||||
Self::PossiblyUnbound { .. } => None,
|
|
||||||
Self::MethodNotAvailable => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
enum NotCallableError<'db> {
|
|
||||||
/// The type is not callable.
|
|
||||||
Type {
|
|
||||||
not_callable_ty: Type<'db>,
|
|
||||||
return_ty: Type<'db>,
|
|
||||||
},
|
|
||||||
/// A single union element is not callable.
|
|
||||||
UnionElement {
|
|
||||||
not_callable_ty: Type<'db>,
|
|
||||||
called_ty: Type<'db>,
|
|
||||||
return_ty: Type<'db>,
|
|
||||||
},
|
|
||||||
/// Multiple (but not all) union elements are not callable.
|
|
||||||
UnionElements {
|
|
||||||
not_callable_tys: Box<[Type<'db>]>,
|
|
||||||
called_ty: Type<'db>,
|
|
||||||
return_ty: Type<'db>,
|
|
||||||
},
|
|
||||||
PossiblyUnboundDunderCall {
|
|
||||||
callable_ty: Type<'db>,
|
|
||||||
return_ty: Type<'db>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'db> NotCallableError<'db> {
|
|
||||||
/// The return type that should be used when a call is not callable.
|
|
||||||
fn return_ty(&self) -> Type<'db> {
|
|
||||||
match self {
|
|
||||||
Self::Type { return_ty, .. } => *return_ty,
|
|
||||||
Self::UnionElement { return_ty, .. } => *return_ty,
|
|
||||||
Self::UnionElements { return_ty, .. } => *return_ty,
|
|
||||||
Self::PossiblyUnboundDunderCall { return_ty, .. } => *return_ty,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The resolved type that was not callable.
|
|
||||||
///
|
|
||||||
/// For unions, returns the union type itself, which may contain a mix of callable and
|
|
||||||
/// non-callable types.
|
|
||||||
fn called_ty(&self) -> Type<'db> {
|
|
||||||
match self {
|
|
||||||
Self::Type {
|
|
||||||
not_callable_ty, ..
|
|
||||||
} => *not_callable_ty,
|
|
||||||
Self::UnionElement { called_ty, .. } => *called_ty,
|
|
||||||
Self::UnionElements { called_ty, .. } => *called_ty,
|
|
||||||
Self::PossiblyUnboundDunderCall {
|
|
||||||
callable_ty: called_ty,
|
|
||||||
..
|
|
||||||
} => *called_ty,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum IterationOutcome<'db> {
|
enum IterationOutcome<'db> {
|
||||||
Iterable {
|
Iterable {
|
||||||
|
|
312
crates/red_knot_python_semantic/src/types/call.rs
Normal file
312
crates/red_knot_python_semantic/src/types/call.rs
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
use super::diagnostic::{TypeCheckDiagnosticsBuilder, CALL_NON_CALLABLE};
|
||||||
|
use super::{Severity, Type, TypeArrayDisplay, UnionBuilder};
|
||||||
|
use crate::Db;
|
||||||
|
use ruff_db::diagnostic::DiagnosticId;
|
||||||
|
use ruff_python_ast as ast;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub(super) enum CallOutcome<'db> {
|
||||||
|
Callable {
|
||||||
|
return_ty: Type<'db>,
|
||||||
|
},
|
||||||
|
RevealType {
|
||||||
|
return_ty: Type<'db>,
|
||||||
|
revealed_ty: Type<'db>,
|
||||||
|
},
|
||||||
|
NotCallable {
|
||||||
|
not_callable_ty: Type<'db>,
|
||||||
|
},
|
||||||
|
Union {
|
||||||
|
called_ty: Type<'db>,
|
||||||
|
outcomes: Box<[CallOutcome<'db>]>,
|
||||||
|
},
|
||||||
|
PossiblyUnboundDunderCall {
|
||||||
|
called_ty: Type<'db>,
|
||||||
|
call_outcome: Box<CallOutcome<'db>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> CallOutcome<'db> {
|
||||||
|
/// Create a new `CallOutcome::Callable` with given return type.
|
||||||
|
pub(super) fn callable(return_ty: Type<'db>) -> CallOutcome<'db> {
|
||||||
|
CallOutcome::Callable { return_ty }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `CallOutcome::NotCallable` with given not-callable type.
|
||||||
|
pub(super) fn not_callable(not_callable_ty: Type<'db>) -> CallOutcome<'db> {
|
||||||
|
CallOutcome::NotCallable { not_callable_ty }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `CallOutcome::RevealType` with given revealed and return types.
|
||||||
|
pub(super) fn revealed(return_ty: Type<'db>, revealed_ty: Type<'db>) -> CallOutcome<'db> {
|
||||||
|
CallOutcome::RevealType {
|
||||||
|
return_ty,
|
||||||
|
revealed_ty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `CallOutcome::Union` with given wrapped outcomes.
|
||||||
|
pub(super) fn union(
|
||||||
|
called_ty: Type<'db>,
|
||||||
|
outcomes: impl IntoIterator<Item = CallOutcome<'db>>,
|
||||||
|
) -> CallOutcome<'db> {
|
||||||
|
CallOutcome::Union {
|
||||||
|
called_ty,
|
||||||
|
outcomes: outcomes.into_iter().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the return type of the call, or `None` if not callable.
|
||||||
|
pub(super) fn return_ty(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||||
|
match self {
|
||||||
|
Self::Callable { return_ty } => Some(*return_ty),
|
||||||
|
Self::RevealType {
|
||||||
|
return_ty,
|
||||||
|
revealed_ty: _,
|
||||||
|
} => Some(*return_ty),
|
||||||
|
Self::NotCallable { not_callable_ty: _ } => None,
|
||||||
|
Self::Union {
|
||||||
|
outcomes,
|
||||||
|
called_ty: _,
|
||||||
|
} => outcomes
|
||||||
|
.iter()
|
||||||
|
// If all outcomes are NotCallable, we return None; if some outcomes are callable
|
||||||
|
// and some are not, we return a union including Unknown.
|
||||||
|
.fold(None, |acc, outcome| {
|
||||||
|
let ty = outcome.return_ty(db);
|
||||||
|
match (acc, ty) {
|
||||||
|
(None, None) => None,
|
||||||
|
(None, Some(ty)) => Some(UnionBuilder::new(db).add(ty)),
|
||||||
|
(Some(builder), ty) => Some(builder.add(ty.unwrap_or(Type::Unknown))),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(UnionBuilder::build),
|
||||||
|
Self::PossiblyUnboundDunderCall { call_outcome, .. } => call_outcome.return_ty(db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the return type of the call, emitting default diagnostics if needed.
|
||||||
|
pub(super) fn unwrap_with_diagnostic<'a>(
|
||||||
|
&self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
node: ast::AnyNodeRef,
|
||||||
|
diagnostics: &'a mut TypeCheckDiagnosticsBuilder<'db>,
|
||||||
|
) -> Type<'db> {
|
||||||
|
match self.return_ty_result(db, node, diagnostics) {
|
||||||
|
Ok(return_ty) => return_ty,
|
||||||
|
Err(NotCallableError::Type {
|
||||||
|
not_callable_ty,
|
||||||
|
return_ty,
|
||||||
|
}) => {
|
||||||
|
diagnostics.add_lint(
|
||||||
|
&CALL_NON_CALLABLE,
|
||||||
|
node,
|
||||||
|
format_args!(
|
||||||
|
"Object of type `{}` is not callable",
|
||||||
|
not_callable_ty.display(db)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return_ty
|
||||||
|
}
|
||||||
|
Err(NotCallableError::UnionElement {
|
||||||
|
not_callable_ty,
|
||||||
|
called_ty,
|
||||||
|
return_ty,
|
||||||
|
}) => {
|
||||||
|
diagnostics.add_lint(
|
||||||
|
&CALL_NON_CALLABLE,
|
||||||
|
node,
|
||||||
|
format_args!(
|
||||||
|
"Object of type `{}` is not callable (due to union element `{}`)",
|
||||||
|
called_ty.display(db),
|
||||||
|
not_callable_ty.display(db),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return_ty
|
||||||
|
}
|
||||||
|
Err(NotCallableError::UnionElements {
|
||||||
|
not_callable_tys,
|
||||||
|
called_ty,
|
||||||
|
return_ty,
|
||||||
|
}) => {
|
||||||
|
diagnostics.add_lint(
|
||||||
|
&CALL_NON_CALLABLE,
|
||||||
|
node,
|
||||||
|
format_args!(
|
||||||
|
"Object of type `{}` is not callable (due to union elements {})",
|
||||||
|
called_ty.display(db),
|
||||||
|
not_callable_tys.display(db),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return_ty
|
||||||
|
}
|
||||||
|
Err(NotCallableError::PossiblyUnboundDunderCall {
|
||||||
|
callable_ty: called_ty,
|
||||||
|
return_ty,
|
||||||
|
}) => {
|
||||||
|
diagnostics.add_lint(
|
||||||
|
&CALL_NON_CALLABLE,
|
||||||
|
node,
|
||||||
|
format_args!(
|
||||||
|
"Object of type `{}` is not callable (possibly unbound `__call__` method)",
|
||||||
|
called_ty.display(db)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return_ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the return type of the call as a result.
|
||||||
|
pub(super) fn return_ty_result<'a>(
|
||||||
|
&self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
node: ast::AnyNodeRef,
|
||||||
|
diagnostics: &'a mut TypeCheckDiagnosticsBuilder<'db>,
|
||||||
|
) -> Result<Type<'db>, NotCallableError<'db>> {
|
||||||
|
match self {
|
||||||
|
Self::Callable { return_ty } => Ok(*return_ty),
|
||||||
|
Self::RevealType {
|
||||||
|
return_ty,
|
||||||
|
revealed_ty,
|
||||||
|
} => {
|
||||||
|
diagnostics.add(
|
||||||
|
node,
|
||||||
|
DiagnosticId::RevealedType,
|
||||||
|
Severity::Info,
|
||||||
|
format_args!("Revealed type is `{}`", revealed_ty.display(db)),
|
||||||
|
);
|
||||||
|
Ok(*return_ty)
|
||||||
|
}
|
||||||
|
Self::NotCallable { not_callable_ty } => Err(NotCallableError::Type {
|
||||||
|
not_callable_ty: *not_callable_ty,
|
||||||
|
return_ty: Type::Unknown,
|
||||||
|
}),
|
||||||
|
Self::PossiblyUnboundDunderCall {
|
||||||
|
called_ty,
|
||||||
|
call_outcome,
|
||||||
|
} => Err(NotCallableError::PossiblyUnboundDunderCall {
|
||||||
|
callable_ty: *called_ty,
|
||||||
|
return_ty: call_outcome.return_ty(db).unwrap_or(Type::Unknown),
|
||||||
|
}),
|
||||||
|
Self::Union {
|
||||||
|
outcomes,
|
||||||
|
called_ty,
|
||||||
|
} => {
|
||||||
|
let mut not_callable = vec![];
|
||||||
|
let mut union_builder = UnionBuilder::new(db);
|
||||||
|
let mut revealed = false;
|
||||||
|
for outcome in outcomes {
|
||||||
|
let return_ty = match outcome {
|
||||||
|
Self::NotCallable { not_callable_ty } => {
|
||||||
|
not_callable.push(*not_callable_ty);
|
||||||
|
Type::Unknown
|
||||||
|
}
|
||||||
|
Self::RevealType {
|
||||||
|
return_ty,
|
||||||
|
revealed_ty: _,
|
||||||
|
} => {
|
||||||
|
if revealed {
|
||||||
|
*return_ty
|
||||||
|
} else {
|
||||||
|
revealed = true;
|
||||||
|
outcome.unwrap_with_diagnostic(db, node, diagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => outcome.unwrap_with_diagnostic(db, node, diagnostics),
|
||||||
|
};
|
||||||
|
union_builder = union_builder.add(return_ty);
|
||||||
|
}
|
||||||
|
let return_ty = union_builder.build();
|
||||||
|
match not_callable[..] {
|
||||||
|
[] => Ok(return_ty),
|
||||||
|
[elem] => Err(NotCallableError::UnionElement {
|
||||||
|
not_callable_ty: elem,
|
||||||
|
called_ty: *called_ty,
|
||||||
|
return_ty,
|
||||||
|
}),
|
||||||
|
_ if not_callable.len() == outcomes.len() => Err(NotCallableError::Type {
|
||||||
|
not_callable_ty: *called_ty,
|
||||||
|
return_ty,
|
||||||
|
}),
|
||||||
|
_ => Err(NotCallableError::UnionElements {
|
||||||
|
not_callable_tys: not_callable.into_boxed_slice(),
|
||||||
|
called_ty: *called_ty,
|
||||||
|
return_ty,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) enum CallDunderResult<'db> {
|
||||||
|
CallOutcome(CallOutcome<'db>),
|
||||||
|
PossiblyUnbound(CallOutcome<'db>),
|
||||||
|
MethodNotAvailable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> CallDunderResult<'db> {
|
||||||
|
pub(super) fn return_ty(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||||
|
match self {
|
||||||
|
Self::CallOutcome(outcome) => outcome.return_ty(db),
|
||||||
|
Self::PossiblyUnbound { .. } => None,
|
||||||
|
Self::MethodNotAvailable => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub(super) enum NotCallableError<'db> {
|
||||||
|
/// The type is not callable.
|
||||||
|
Type {
|
||||||
|
not_callable_ty: Type<'db>,
|
||||||
|
return_ty: Type<'db>,
|
||||||
|
},
|
||||||
|
/// A single union element is not callable.
|
||||||
|
UnionElement {
|
||||||
|
not_callable_ty: Type<'db>,
|
||||||
|
called_ty: Type<'db>,
|
||||||
|
return_ty: Type<'db>,
|
||||||
|
},
|
||||||
|
/// Multiple (but not all) union elements are not callable.
|
||||||
|
UnionElements {
|
||||||
|
not_callable_tys: Box<[Type<'db>]>,
|
||||||
|
called_ty: Type<'db>,
|
||||||
|
return_ty: Type<'db>,
|
||||||
|
},
|
||||||
|
PossiblyUnboundDunderCall {
|
||||||
|
callable_ty: Type<'db>,
|
||||||
|
return_ty: Type<'db>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> NotCallableError<'db> {
|
||||||
|
/// The return type that should be used when a call is not callable.
|
||||||
|
pub(super) fn return_ty(&self) -> Type<'db> {
|
||||||
|
match self {
|
||||||
|
Self::Type { return_ty, .. } => *return_ty,
|
||||||
|
Self::UnionElement { return_ty, .. } => *return_ty,
|
||||||
|
Self::UnionElements { return_ty, .. } => *return_ty,
|
||||||
|
Self::PossiblyUnboundDunderCall { return_ty, .. } => *return_ty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The resolved type that was not callable.
|
||||||
|
///
|
||||||
|
/// For unions, returns the union type itself, which may contain a mix of callable and
|
||||||
|
/// non-callable types.
|
||||||
|
pub(super) fn called_ty(&self) -> Type<'db> {
|
||||||
|
match self {
|
||||||
|
Self::Type {
|
||||||
|
not_callable_ty, ..
|
||||||
|
} => *not_callable_ty,
|
||||||
|
Self::UnionElement { called_ty, .. } => *called_ty,
|
||||||
|
Self::UnionElements { called_ty, .. } => *called_ty,
|
||||||
|
Self::PossiblyUnboundDunderCall {
|
||||||
|
callable_ty: called_ty,
|
||||||
|
..
|
||||||
|
} => *called_ty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue