Skip to content

Commit

Permalink
[red-knot] Prefix Type::call and dunder_call with try (#16261)
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser authored Feb 20, 2025
1 parent 16d0625 commit fb09d63
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 87 deletions.
1 change: 0 additions & 1 deletion crates/red_knot_python_semantic/src/semantic_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ impl<'db> SemanticIndex<'db> {
}

/// Returns an iterator over all ancestors of `scope`, starting with `scope` itself.
#[allow(unused)]
pub(crate) fn ancestor_scopes(&self, scope: FileScopeId) -> AncestorsIter {
AncestorsIter::new(self, scope)
}
Expand Down
105 changes: 34 additions & 71 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1468,7 +1468,7 @@ impl<'db> Type<'db> {
};

if let Ok(Type::BooleanLiteral(bool_val)) = bool_method
.call_bound(db, instance_ty, &CallArguments::positional([]))
.try_call_bound(db, instance_ty, &CallArguments::positional([]))
.map(|outcome| outcome.return_type(db))
{
bool_val.into()
Expand Down Expand Up @@ -1541,20 +1541,23 @@ impl<'db> Type<'db> {
return usize_len.try_into().ok().map(Type::IntLiteral);
}

let return_ty = match self.call_dunder(db, "__len__", &CallArguments::positional([*self])) {
Ok(outcome) | Err(CallDunderError::PossiblyUnbound(outcome)) => outcome.return_type(db),
let return_ty =
match self.try_call_dunder(db, "__len__", &CallArguments::positional([*self])) {
Ok(outcome) | Err(CallDunderError::PossiblyUnbound(outcome)) => {
outcome.return_type(db)
}

// TODO: emit a diagnostic
Err(err) => err.return_type(db)?,
};
// TODO: emit a diagnostic
Err(err) => err.return_type(db)?,
};

non_negative_int_literal(db, return_ty)
}

/// Calls `self`
///
/// Returns `Ok` if the call with the given arguments is successful and `Err` otherwise.
fn call(
fn try_call(
self,
db: &'db dyn Db,
arguments: &CallArguments<'_, 'db>,
Expand Down Expand Up @@ -1672,7 +1675,7 @@ impl<'db> Type<'db> {

instance_ty @ Type::Instance(_) => {
instance_ty
.call_dunder(db, "__call__", &arguments.with_self(instance_ty))
.try_call_dunder(db, "__call__", &arguments.with_self(instance_ty))
.map_err(|err| match err {
CallDunderError::Call(CallError::NotCallable { .. }) => {
// Turn "`<type of illegal '__call__'>` not callable" into
Expand Down Expand Up @@ -1712,7 +1715,7 @@ impl<'db> Type<'db> {
Type::Dynamic(_) => Ok(CallOutcome::Single(CallBinding::from_return_type(self))),

Type::Union(union) => {
CallOutcome::try_call_union(db, union, |element| element.call(db, arguments))
CallOutcome::try_call_union(db, union, |element| element.try_call(db, arguments))
}

Type::Intersection(_) => Ok(CallOutcome::Single(CallBinding::from_return_type(
Expand All @@ -1731,7 +1734,7 @@ impl<'db> Type<'db> {
/// `receiver_ty` must be `Type::Instance(_)` or `Type::ClassLiteral`.
///
/// TODO: handle `super()` objects properly
fn call_bound(
fn try_call_bound(
self,
db: &'db dyn Db,
receiver_ty: &Type<'db>,
Expand All @@ -1743,16 +1746,16 @@ impl<'db> Type<'db> {
Type::FunctionLiteral(..) => {
// Functions are always descriptors, so this would effectively call
// the function with the instance as the first argument
self.call(db, &arguments.with_self(*receiver_ty))
self.try_call(db, &arguments.with_self(*receiver_ty))
}

Type::Instance(_) | Type::ClassLiteral(_) => {
// TODO descriptor protocol. For now, assume non-descriptor and call without `self` argument.
self.call(db, arguments)
self.try_call(db, arguments)
}

Type::Union(union) => CallOutcome::try_call_union(db, union, |element| {
element.call_bound(db, receiver_ty, arguments)
element.try_call_bound(db, receiver_ty, arguments)
}),

Type::Intersection(_) => Ok(CallOutcome::Single(CallBinding::from_return_type(
Expand All @@ -1769,16 +1772,16 @@ impl<'db> Type<'db> {
}

/// Look up a dunder method on the meta type of `self` and call it.
fn call_dunder(
fn try_call_dunder(
self,
db: &'db dyn Db,
name: &str,
arguments: &CallArguments<'_, 'db>,
) -> Result<CallOutcome<'db>, CallDunderError<'db>> {
match self.to_meta_type(db).member(db, name) {
Symbol::Type(callable_ty, Boundness::Bound) => Ok(callable_ty.call(db, arguments)?),
Symbol::Type(callable_ty, Boundness::Bound) => Ok(callable_ty.try_call(db, arguments)?),
Symbol::Type(callable_ty, Boundness::PossiblyUnbound) => {
let call = callable_ty.call(db, arguments)?;
let call = callable_ty.try_call(db, arguments)?;
Err(CallDunderError::PossiblyUnbound(call))
}
Symbol::Unbound => Err(CallDunderError::MethodNotAvailable),
Expand All @@ -1801,12 +1804,12 @@ impl<'db> Type<'db> {
}

let dunder_iter_result =
self.call_dunder(db, "__iter__", &CallArguments::positional([self]));
self.try_call_dunder(db, "__iter__", &CallArguments::positional([self]));
match &dunder_iter_result {
Ok(outcome) | Err(CallDunderError::PossiblyUnbound(outcome)) => {
let iterator_ty = outcome.return_type(db);

return match iterator_ty.call_dunder(
return match iterator_ty.try_call_dunder(
db,
"__next__",
&CallArguments::positional([iterator_ty]),
Expand Down Expand Up @@ -1855,7 +1858,7 @@ impl<'db> Type<'db> {
//
// TODO(Alex) this is only valid if the `__getitem__` method is annotated as
// accepting `int` or `SupportsIndex`
match self.call_dunder(
match self.try_call_dunder(
db,
"__getitem__",
&CallArguments::positional([self, KnownClass::Int.to_instance(db)]),
Expand Down Expand Up @@ -2693,52 +2696,8 @@ pub enum KnownInstanceType<'db> {
}

impl<'db> KnownInstanceType<'db> {
pub const fn as_str(self) -> &'static str {
match self {
Self::Annotated => "Annotated",
Self::Literal => "Literal",
Self::LiteralString => "LiteralString",
Self::Optional => "Optional",
Self::Union => "Union",
Self::TypeVar(_) => "TypeVar",
Self::NoReturn => "NoReturn",
Self::Never => "Never",
Self::Any => "Any",
Self::Tuple => "Tuple",
Self::Type => "Type",
Self::TypeAliasType(_) => "TypeAliasType",
Self::TypingSelf => "Self",
Self::Final => "Final",
Self::ClassVar => "ClassVar",
Self::Callable => "Callable",
Self::Concatenate => "Concatenate",
Self::Unpack => "Unpack",
Self::Required => "Required",
Self::NotRequired => "NotRequired",
Self::TypeAlias => "TypeAlias",
Self::TypeGuard => "TypeGuard",
Self::TypeIs => "TypeIs",
Self::List => "List",
Self::Dict => "Dict",
Self::DefaultDict => "DefaultDict",
Self::Set => "Set",
Self::FrozenSet => "FrozenSet",
Self::Counter => "Counter",
Self::Deque => "Deque",
Self::ChainMap => "ChainMap",
Self::OrderedDict => "OrderedDict",
Self::ReadOnly => "ReadOnly",
Self::Unknown => "Unknown",
Self::AlwaysTruthy => "AlwaysTruthy",
Self::AlwaysFalsy => "AlwaysFalsy",
Self::Not => "Not",
Self::Intersection => "Intersection",
Self::TypeOf => "TypeOf",
}
}

/// Evaluate the known instance in boolean context
pub const fn bool(self) -> Truthiness {
pub(crate) const fn bool(self) -> Truthiness {
match self {
Self::Annotated
| Self::Literal
Expand Down Expand Up @@ -2783,7 +2742,7 @@ impl<'db> KnownInstanceType<'db> {
}

/// Return the repr of the symbol at runtime
pub fn repr(self, db: &'db dyn Db) -> &'db str {
pub(crate) fn repr(self, db: &'db dyn Db) -> &'db str {
match self {
Self::Annotated => "typing.Annotated",
Self::Literal => "typing.Literal",
Expand Down Expand Up @@ -2828,7 +2787,7 @@ impl<'db> KnownInstanceType<'db> {
}

/// Return the [`KnownClass`] which this symbol is an instance of
pub const fn class(self) -> KnownClass {
pub(crate) const fn class(self) -> KnownClass {
match self {
Self::Annotated => KnownClass::SpecialForm,
Self::Literal => KnownClass::SpecialForm,
Expand Down Expand Up @@ -2877,16 +2836,20 @@ impl<'db> KnownInstanceType<'db> {
/// For example, the symbol `typing.Literal` is an instance of `typing._SpecialForm`,
/// so `KnownInstanceType::Literal.instance_fallback(db)`
/// returns `Type::Instance(InstanceType { class: <typing._SpecialForm> })`.
pub fn instance_fallback(self, db: &dyn Db) -> Type {
pub(crate) fn instance_fallback(self, db: &dyn Db) -> Type {
self.class().to_instance(db)
}

/// Return `true` if this symbol is an instance of `class`.
pub fn is_instance_of(self, db: &'db dyn Db, class: Class<'db>) -> bool {
pub(crate) fn is_instance_of(self, db: &'db dyn Db, class: Class<'db>) -> bool {
self.class().is_subclass_of(db, class)
}

pub fn try_from_file_and_name(db: &'db dyn Db, file: File, symbol_name: &str) -> Option<Self> {
pub(crate) fn try_from_file_and_name(
db: &'db dyn Db,
file: File,
symbol_name: &str,
) -> Option<Self> {
let candidate = match symbol_name {
"Any" => Self::Any,
"ClassVar" => Self::ClassVar,
Expand Down Expand Up @@ -2937,7 +2900,7 @@ impl<'db> KnownInstanceType<'db> {
///
/// Most variants can only exist in one module, which is the same as `self.class().canonical_module()`.
/// Some variants could validly be defined in either `typing` or `typing_extensions`, however.
pub fn check_module(self, module: KnownModule) -> bool {
pub(crate) fn check_module(self, module: KnownModule) -> bool {
match self {
Self::Any
| Self::ClassVar
Expand Down Expand Up @@ -3668,7 +3631,7 @@ impl<'db> Class<'db> {
// TODO: Other keyword arguments?
let arguments = CallArguments::positional([name, bases, namespace]);

let return_ty_result = match metaclass.call(db, &arguments) {
let return_ty_result = match metaclass.try_call(db, &arguments) {
Ok(outcome) => Ok(outcome.return_type(db)),

Err(CallError::NotCallable { not_callable_ty }) => Err(MetaclassError {
Expand Down
29 changes: 16 additions & 13 deletions crates/red_knot_python_semantic/src/types/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1616,7 +1616,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}

let target_ty = enter_ty
.call(self.db(), &CallArguments::positional([context_expression_ty]))
.try_call(self.db(), &CallArguments::positional([context_expression_ty]))
.map(|outcome| outcome.return_type(self.db()))
.unwrap_or_else(|err| {
// TODO: Use more specific error messages for the different error cases.
Expand Down Expand Up @@ -1659,7 +1659,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}

if exit_ty
.call(
.try_call(
self.db(),
&CallArguments::positional([
context_manager_ty,
Expand Down Expand Up @@ -2209,7 +2209,7 @@ impl<'db> TypeInferenceBuilder<'db> {
if let Symbol::Type(class_member, boundness) =
class.class_member(self.db(), op.in_place_dunder())
{
let call = class_member.call(
let call = class_member.try_call(
self.db(),
&CallArguments::positional([target_type, value_type]),
);
Expand Down Expand Up @@ -3247,7 +3247,7 @@ impl<'db> TypeInferenceBuilder<'db> {
.unwrap_or_default();

let call_arguments = self.infer_arguments(arguments, parameter_expectations);
let call = function_type.call(self.db(), &call_arguments);
let call = function_type.try_call(self.db(), &call_arguments);

match call {
Ok(outcome) => {
Expand Down Expand Up @@ -3747,7 +3747,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}
};

match operand_type.call_dunder(
match operand_type.try_call_dunder(
self.db(),
unary_dunder_method,
&CallArguments::positional([operand_type]),
Expand Down Expand Up @@ -3996,15 +3996,15 @@ impl<'db> TypeInferenceBuilder<'db> {
&& rhs_reflected != left_class.member(self.db(), reflected_dunder)
{
return right_ty
.call_dunder(
.try_call_dunder(
self.db(),
reflected_dunder,
&CallArguments::positional([right_ty, left_ty]),
)
.map(|outcome| outcome.return_type(self.db()))
.or_else(|_| {
left_ty
.call_dunder(
.try_call_dunder(
self.db(),
op.dunder(),
&CallArguments::positional([left_ty, right_ty]),
Expand All @@ -4020,7 +4020,7 @@ impl<'db> TypeInferenceBuilder<'db> {
left_class.member(self.db(), op.dunder())
{
class_member
.call(self.db(), &CallArguments::positional([left_ty, right_ty]))
.try_call(self.db(), &CallArguments::positional([left_ty, right_ty]))
.map(|outcome| outcome.return_type(self.db()))
.ok()
} else {
Expand All @@ -4036,7 +4036,10 @@ impl<'db> TypeInferenceBuilder<'db> {
{
// TODO: Use `call_dunder`
class_member
.call(self.db(), &CallArguments::positional([right_ty, left_ty]))
.try_call(
self.db(),
&CallArguments::positional([right_ty, left_ty]),
)
.map(|outcome| outcome.return_type(self.db()))
.ok()
} else {
Expand Down Expand Up @@ -4610,7 +4613,7 @@ impl<'db> TypeInferenceBuilder<'db> {
// TODO: How do we want to handle possibly unbound dunder methods?
match left.class.class_member(db, op.dunder()) {
Symbol::Type(class_member_dunder, Boundness::Bound) => class_member_dunder
.call(
.try_call(
db,
&CallArguments::positional([Type::Instance(left), Type::Instance(right)]),
)
Expand Down Expand Up @@ -4660,7 +4663,7 @@ impl<'db> TypeInferenceBuilder<'db> {
Symbol::Type(contains_dunder, Boundness::Bound) => {
// If `__contains__` is available, it is used directly for the membership test.
contains_dunder
.call(
.try_call(
db,
&CallArguments::positional([Type::Instance(right), Type::Instance(left)]),
)
Expand Down Expand Up @@ -4917,7 +4920,7 @@ impl<'db> TypeInferenceBuilder<'db> {
// If the class defines `__getitem__`, return its return type.
//
// See: https://docs.python.org/3/reference/datamodel.html#class-getitem-versus-getitem
match value_ty.call_dunder(
match value_ty.try_call_dunder(
self.db(),
"__getitem__",
&CallArguments::positional([value_ty, slice_ty]),
Expand Down Expand Up @@ -4981,7 +4984,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}

return ty
.call(self.db(), &CallArguments::positional([value_ty, slice_ty]))
.try_call(self.db(), &CallArguments::positional([value_ty, slice_ty]))
.map(|outcome| outcome.return_type(self.db()))
.unwrap_or_else(|err| {
self.context.report_lint(
Expand Down
2 changes: 0 additions & 2 deletions crates/ruff_db/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ mod tests {
///
/// ## Panics
/// If there are pending database snapshots.
#[allow(unused)]
pub(crate) fn take_salsa_events(&mut self) -> Vec<salsa::Event> {
let inner = Arc::get_mut(&mut self.events)
.expect("expected no pending salsa database snapshots.");
Expand All @@ -86,7 +85,6 @@ mod tests {
///
/// ## Panics
/// If there are pending database snapshots.
#[allow(unused)]
pub(crate) fn clear_salsa_events(&mut self) {
self.take_salsa_events();
}
Expand Down

0 comments on commit fb09d63

Please sign in to comment.