Skip to content

Commit 12dc9eb

Browse files
committed
Add tests for keyword-only, keyword-variadic
1 parent 4beb0f8 commit 12dc9eb

File tree

2 files changed

+143
-1
lines changed

2 files changed

+143
-1
lines changed

crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,148 @@ def mixed(a: int, /, b: int) -> None: ...
708708
static_assert(not is_subtype_of(CallableTypeFromFunction[variadic], CallableTypeFromFunction[mixed]))
709709
```
710710

711+
#### Keyword-only
712+
713+
For keyword-only parameters, the name matters:
714+
715+
```py
716+
from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
717+
718+
def keyword_int(*, a: int) -> None: ...
719+
def keyword_float(*, a: float) -> None: ...
720+
def keyword_b(*, b: int) -> None: ...
721+
722+
static_assert(is_subtype_of(CallableTypeFromFunction[keyword_float], CallableTypeFromFunction[keyword_int]))
723+
static_assert(not is_subtype_of(CallableTypeFromFunction[keyword_int], CallableTypeFromFunction[keyword_float]))
724+
static_assert(not is_subtype_of(CallableTypeFromFunction[keyword_int], CallableTypeFromFunction[keyword_b]))
725+
```
726+
727+
But, the order of the keyword-only parameters does not:
728+
729+
```py
730+
def keyword_ab(*, a: float, b: float) -> None: ...
731+
def keyword_ba(*, b: int, a: int) -> None: ...
732+
733+
static_assert(is_subtype_of(CallableTypeFromFunction[keyword_ab], CallableTypeFromFunction[keyword_ba]))
734+
static_assert(not is_subtype_of(CallableTypeFromFunction[keyword_ba], CallableTypeFromFunction[keyword_ab]))
735+
```
736+
737+
#### Keyword-only with default
738+
739+
```py
740+
from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
741+
742+
def float_with_default(*, a: float = 1) -> None: ...
743+
def int_with_default(*, a: int = 1) -> None: ...
744+
def int_keyword(*, a: int) -> None: ...
745+
def empty() -> None: ...
746+
747+
static_assert(is_subtype_of(CallableTypeFromFunction[float_with_default], CallableTypeFromFunction[int_with_default]))
748+
static_assert(not is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[float_with_default]))
749+
750+
static_assert(is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[int_keyword]))
751+
static_assert(not is_subtype_of(CallableTypeFromFunction[int_keyword], CallableTypeFromFunction[int_with_default]))
752+
753+
static_assert(is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[empty]))
754+
static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[int_with_default]))
755+
```
756+
757+
Here, we mix keyword-only parameter with and without the default value:
758+
759+
```py
760+
def mixed(*, b: int = 1, a: int) -> None: ...
761+
762+
static_assert(is_subtype_of(CallableTypeFromFunction[mixed], CallableTypeFromFunction[int_keyword]))
763+
static_assert(not is_subtype_of(CallableTypeFromFunction[int_keyword], CallableTypeFromFunction[mixed]))
764+
```
765+
766+
#### Keyword-only with positional
767+
768+
```py
769+
from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
770+
771+
def keywords1(*, a: int, b: int) -> None: ...
772+
def standard(b: float, a: float) -> None: ...
773+
774+
static_assert(is_subtype_of(CallableTypeFromFunction[standard], CallableTypeFromFunction[keywords1]))
775+
static_assert(not is_subtype_of(CallableTypeFromFunction[keywords1], CallableTypeFromFunction[standard]))
776+
```
777+
778+
The subtype can include additional standard parameters as long as it has the default value:
779+
780+
```py
781+
def standard_with_default(b: float, a: float, c: float = 1) -> None: ...
782+
def standard_without_default(b: float, a: float, c: float) -> None: ...
783+
784+
static_assert(is_subtype_of(CallableTypeFromFunction[standard_with_default], CallableTypeFromFunction[keywords1]))
785+
static_assert(not is_subtype_of(CallableTypeFromFunction[standard_without_default], CallableTypeFromFunction[keywords1]))
786+
```
787+
788+
Here, we mix keyword-only parameters with standard parameters:
789+
790+
```py
791+
def keywords2(*, a: int, c: int, b: int) -> None: ...
792+
def mixed(b: float, a: float, *, c: float) -> None: ...
793+
794+
static_assert(is_subtype_of(CallableTypeFromFunction[mixed], CallableTypeFromFunction[keywords2]))
795+
static_assert(not is_subtype_of(CallableTypeFromFunction[keywords2], CallableTypeFromFunction[mixed]))
796+
```
797+
798+
But, we shouldn't consider any unmatched positional-only parameters:
799+
800+
```py
801+
def mixed_positional(b: float, /, a: float) -> None: ...
802+
803+
static_assert(not is_subtype_of(CallableTypeFromFunction[mixed_positional], CallableTypeFromFunction[keywords1]))
804+
```
805+
806+
#### Keyword-variadic
807+
808+
The name of the keyword-variadic parameter does not need to be the same in the subtype.
809+
810+
```py
811+
from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
812+
813+
def kwargs_float(**kwargs2: float) -> None: ...
814+
def kwargs_int(**kwargs1: int) -> None: ...
815+
816+
static_assert(is_subtype_of(CallableTypeFromFunction[kwargs_float], CallableTypeFromFunction[kwargs_int]))
817+
static_assert(not is_subtype_of(CallableTypeFromFunction[kwargs_int], CallableTypeFromFunction[kwargs_float]))
818+
```
819+
820+
A variadic parameter can be omitted in the subtype:
821+
822+
```py
823+
def empty() -> None: ...
824+
825+
static_assert(is_subtype_of(CallableTypeFromFunction[kwargs_int], CallableTypeFromFunction[empty]))
826+
static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[kwargs_int]))
827+
```
828+
829+
#### Keyword-variadic with keyword-only
830+
831+
If the subtype has a variadic parameter then any unmatched positional-only parameter from the
832+
supertype should be checked against the variadic parameter.
833+
834+
```py
835+
from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert
836+
837+
def kwargs(**kwargs: float) -> None: ...
838+
def keyword_only(*, a: int, b: float) -> None: ...
839+
def keyword_variadic(*, a: int, **kwargs: int) -> None: ...
840+
841+
static_assert(is_subtype_of(CallableTypeFromFunction[kwargs], CallableTypeFromFunction[keyword_only]))
842+
static_assert(is_subtype_of(CallableTypeFromFunction[kwargs], CallableTypeFromFunction[keyword_variadic]))
843+
```
844+
845+
This is valid only for positional-only parameter, not any other parameter kind:
846+
847+
```py
848+
def mixed(a: int, *, b: int) -> None: ...
849+
850+
static_assert(not is_subtype_of(CallableTypeFromFunction[kwargs], CallableTypeFromFunction[mixed]))
851+
```
852+
711853
#### Empty
712854

713855
When the supertype has an empty list of parameters, then the subtype can have any kind of parameters

crates/red_knot_python_semantic/src/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4828,7 +4828,7 @@ impl<'db> GeneralCallableType<'db> {
48284828
| ParameterKind::KeywordOnly {
48294829
default_ty: self_default,
48304830
} => {
4831-
if self_default.is_some() && other_default.is_none() {
4831+
if self_default.is_none() && other_default.is_some() {
48324832
return false;
48334833
}
48344834
if !is_subtype(

0 commit comments

Comments
 (0)