mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 22:31:23 +00:00
[red-knot] Check gradual equivalence between callable types (#16634)
This commit is contained in:
parent
08fa9b4a90
commit
58d5fe982e
2 changed files with 110 additions and 0 deletions
|
@ -62,4 +62,67 @@ static_assert(not is_gradual_equivalent_to(tuple[str, int], tuple[str, int, byte
|
||||||
static_assert(not is_gradual_equivalent_to(tuple[str, int], tuple[int, str]))
|
static_assert(not is_gradual_equivalent_to(tuple[str, int], tuple[int, str]))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Callable
|
||||||
|
|
||||||
|
```py
|
||||||
|
from knot_extensions import Unknown, CallableTypeFromFunction, is_gradual_equivalent_to, static_assert
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
static_assert(is_gradual_equivalent_to(Callable[..., int], Callable[..., int]))
|
||||||
|
static_assert(is_gradual_equivalent_to(Callable[..., Any], Callable[..., Unknown]))
|
||||||
|
static_assert(is_gradual_equivalent_to(Callable[[int, Any], None], Callable[[int, Unknown], None]))
|
||||||
|
|
||||||
|
static_assert(not is_gradual_equivalent_to(Callable[[int, Any], None], Callable[[Any, int], None]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(Callable[[int, str], None], Callable[[int, str, bytes], None]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(Callable[..., None], Callable[[], None]))
|
||||||
|
```
|
||||||
|
|
||||||
|
A function with no explicit return type should be gradual equivalent to a callable with a return
|
||||||
|
type of `Any`.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def f1():
|
||||||
|
return
|
||||||
|
|
||||||
|
static_assert(is_gradual_equivalent_to(CallableTypeFromFunction[f1], Callable[[], Any]))
|
||||||
|
```
|
||||||
|
|
||||||
|
And, similarly for parameters with no annotations.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def f2(a, b) -> None:
|
||||||
|
return
|
||||||
|
|
||||||
|
static_assert(is_gradual_equivalent_to(CallableTypeFromFunction[f2], Callable[[Any, Any], None]))
|
||||||
|
```
|
||||||
|
|
||||||
|
Additionally, as per the spec, a function definition that includes both `*args` and `**kwargs`
|
||||||
|
parameter that are annotated as `Any` or kept unannotated should be gradual equivalent to a callable
|
||||||
|
with `...` as the parameter type.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def variadic_without_annotation(*args, **kwargs):
|
||||||
|
return
|
||||||
|
|
||||||
|
def variadic_with_annotation(*args: Any, **kwargs: Any) -> Any:
|
||||||
|
return
|
||||||
|
|
||||||
|
static_assert(is_gradual_equivalent_to(CallableTypeFromFunction[variadic_without_annotation], Callable[..., Any]))
|
||||||
|
static_assert(is_gradual_equivalent_to(CallableTypeFromFunction[variadic_with_annotation], Callable[..., Any]))
|
||||||
|
```
|
||||||
|
|
||||||
|
But, a function with either `*args` or `**kwargs` is not gradual equivalent to a callable with `...`
|
||||||
|
as the parameter type.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def variadic_args(*args):
|
||||||
|
return
|
||||||
|
|
||||||
|
def variadic_kwargs(**kwargs):
|
||||||
|
return
|
||||||
|
|
||||||
|
static_assert(not is_gradual_equivalent_to(CallableTypeFromFunction[variadic_args], Callable[..., Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(CallableTypeFromFunction[variadic_kwargs], Callable[..., Any]))
|
||||||
|
```
|
||||||
|
|
||||||
[materializations]: https://typing.readthedocs.io/en/latest/spec/glossary.html#term-materialize
|
[materializations]: https://typing.readthedocs.io/en/latest/spec/glossary.html#term-materialize
|
||||||
|
|
|
@ -956,6 +956,11 @@ impl<'db> Type<'db> {
|
||||||
first.is_gradual_equivalent_to(db, second)
|
first.is_gradual_equivalent_to(db, second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
Type::Callable(CallableType::General(first)),
|
||||||
|
Type::Callable(CallableType::General(second)),
|
||||||
|
) => first.is_gradual_equivalent_to(db, second),
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4575,6 +4580,48 @@ impl<'db> GeneralCallableType<'db> {
|
||||||
.return_ty
|
.return_ty
|
||||||
.is_some_and(|return_type| return_type.is_fully_static(db))
|
.is_some_and(|return_type| return_type.is_fully_static(db))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return `true` if `self` has exactly the same set of possible static materializations as
|
||||||
|
/// `other` (if `self` represents the same set of possible sets of possible runtime objects as
|
||||||
|
/// `other`).
|
||||||
|
pub(crate) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||||
|
let self_signature = self.signature(db);
|
||||||
|
let other_signature = other.signature(db);
|
||||||
|
|
||||||
|
if self_signature.parameters().len() != other_signature.parameters().len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check gradual equivalence between the two optional types. In the context of a callable
|
||||||
|
// type, the `None` type represents an `Unknown` type.
|
||||||
|
let are_optional_types_gradually_equivalent =
|
||||||
|
|self_type: Option<Type<'db>>, other_type: Option<Type<'db>>| {
|
||||||
|
self_type
|
||||||
|
.unwrap_or(Type::unknown())
|
||||||
|
.is_gradual_equivalent_to(db, other_type.unwrap_or(Type::unknown()))
|
||||||
|
};
|
||||||
|
|
||||||
|
if !are_optional_types_gradually_equivalent(
|
||||||
|
self_signature.return_ty,
|
||||||
|
other_signature.return_ty,
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// N.B. We don't need to explicitly check for the use of gradual form (`...`) in the
|
||||||
|
// parameters because it is internally represented by adding `*Any` and `**Any` to the
|
||||||
|
// parameter list.
|
||||||
|
self_signature
|
||||||
|
.parameters()
|
||||||
|
.iter()
|
||||||
|
.zip(other_signature.parameters().iter())
|
||||||
|
.all(|(self_param, other_param)| {
|
||||||
|
are_optional_types_gradually_equivalent(
|
||||||
|
self_param.annotated_type(),
|
||||||
|
other_param.annotated_type(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type that represents callable objects.
|
/// A type that represents callable objects.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue