[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

@ -169,6 +169,15 @@ def f(*args: int) -> int:
reveal_type(f(1, 2, 3)) # revealed: int reveal_type(f(1, 2, 3)) # revealed: int
``` ```
### Multiple keyword arguments map to keyword variadic parameter
```py
def f(**kwargs: int) -> int:
return 1
reveal_type(f(foo=1, bar=2)) # revealed: int
```
## Missing arguments ## Missing arguments
### No defaults or variadic ### No defaults or variadic

View file

@ -2021,11 +2021,11 @@ impl<'db> Type<'db> {
return CallOutcome::callable(binding); return CallOutcome::callable(binding);
}; };
let Some(casted_ty) = arguments.first_argument() else { if let Some(casted_ty) = arguments.first_argument() {
return CallOutcome::callable(binding); binding.set_return_ty(casted_ty);
}; };
CallOutcome::casted(binding, casted_ty) CallOutcome::callable(binding)
} }
_ => CallOutcome::callable(binding), _ => CallOutcome::callable(binding),
@ -3877,7 +3877,6 @@ impl<'db> Class<'db> {
| CallOutcome::RevealType { binding, .. } | CallOutcome::RevealType { binding, .. }
| CallOutcome::StaticAssertionError { binding, .. } | CallOutcome::StaticAssertionError { binding, .. }
| CallOutcome::AssertType { binding, .. } => Ok(binding.return_ty()), | 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)); 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>, binding: CallBinding<'db>,
asserted_ty: Type<'db>, asserted_ty: Type<'db>,
}, },
Cast {
binding: CallBinding<'db>,
casted_ty: Type<'db>,
},
} }
impl<'db> CallOutcome<'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> { pub(super) fn callable(binding: CallBinding<'db>) -> CallOutcome<'db> {
CallOutcome::Callable { binding } 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. /// 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>> { pub(super) fn return_ty(&self, db: &'db dyn Db) -> Option<Type<'db>> {
match self { match self {
@ -128,10 +119,6 @@ impl<'db> CallOutcome<'db> {
binding, binding,
asserted_ty: _, asserted_ty: _,
} => Some(binding.return_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()) 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 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]); let union = UnionType::from_elements(db, [existing, *argument_ty]);
parameter_tys[index].replace(union); parameter_tys[index].replace(union);
} else { } else {