[red-knot] remove CallOutcome::Cast variant (#15461)

## Summary

Simplification follow-up to #15413.

There's no need to have a dedicated `CallOutcome` variant for every
known function, it's only necessary if the special-cased behavior of the
known function includes emitting extra diagnostics. For `typing.cast`,
there's no such need; we can use the regular `Callable` outcome variant,
and update the return type according to the cast. (This is the same way
we already handle `len`.)

One reason to avoid proliferating unnecessary `CallOutcome` variants is
that currently we have to explicitly add emitting call-binding
diagnostics, for each outcome variant. So we were previously wrongly
silencing any binding diagnostics on calls to `typing.cast`. Fixing this
revealed a separate bug, that we were emitting a bogus error anytime
more than one keyword argument mapped to a `**kwargs` parameter. So this
PR also adds test and fix for that bug.

## Test Plan

Existing `cast` tests pass unchanged, added new test for `**kwargs` bug.
This commit is contained in:
Carl Meyer 2025-01-13 10:58:53 -08:00 committed by GitHub
parent 5ad546f187
commit d54c19b983
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 14 additions and 23 deletions

View file

@ -2021,11 +2021,11 @@ impl<'db> Type<'db> {
return CallOutcome::callable(binding);
};
let Some(casted_ty) = arguments.first_argument() else {
return CallOutcome::callable(binding);
if let Some(casted_ty) = arguments.first_argument() {
binding.set_return_ty(casted_ty);
};
CallOutcome::casted(binding, casted_ty)
CallOutcome::callable(binding)
}
_ => CallOutcome::callable(binding),
@ -3877,7 +3877,6 @@ impl<'db> Class<'db> {
| CallOutcome::RevealType { binding, .. }
| CallOutcome::StaticAssertionError { binding, .. }
| CallOutcome::AssertType { binding, .. } => Ok(binding.return_ty()),
CallOutcome::Cast { casted_ty, .. } => Ok(casted_ty),
};
return return_ty_result.map(|ty| ty.to_meta_type(db));

View file

@ -48,14 +48,10 @@ pub(super) enum CallOutcome<'db> {
binding: CallBinding<'db>,
asserted_ty: Type<'db>,
},
Cast {
binding: CallBinding<'db>,
casted_ty: Type<'db>,
},
}
impl<'db> CallOutcome<'db> {
/// Create a new `CallOutcome::Callable` with given return type.
/// Create a new `CallOutcome::Callable` with given binding.
pub(super) fn callable(binding: CallBinding<'db>) -> CallOutcome<'db> {
CallOutcome::Callable { binding }
}
@ -92,11 +88,6 @@ impl<'db> CallOutcome<'db> {
}
}
/// Create a new `CallOutcome::Casted` with given casted and return types.
pub(super) fn casted(binding: CallBinding<'db>, casted_ty: Type<'db>) -> CallOutcome<'db> {
CallOutcome::Cast { binding, casted_ty }
}
/// 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 {
@ -128,10 +119,6 @@ impl<'db> CallOutcome<'db> {
binding,
asserted_ty: _,
} => Some(binding.return_ty()),
Self::Cast {
binding: _,
casted_ty,
} => Some(*casted_ty),
}
}
@ -360,10 +347,6 @@ impl<'db> CallOutcome<'db> {
Ok(binding.return_ty())
}
Self::Cast {
binding: _,
casted_ty,
} => Ok(*casted_ty),
}
}
}

View file

@ -84,7 +84,7 @@ pub(crate) fn bind_call<'db>(
}
}
if let Some(existing) = parameter_tys[index].replace(*argument_ty) {
if parameter.is_variadic() {
if parameter.is_variadic() || parameter.is_keyword_variadic() {
let union = UnionType::from_elements(db, [existing, *argument_ty]);
parameter_tys[index].replace(union);
} else {