[ty] Complete support for ParamSpec#21445
Conversation
P.args and P.kwargsP.args and P.kwargs
Diagnostic diff on typing conformance testsChanges were detected when running ty on typing conformance tests--- old-output.txt 2025-12-05 16:25:18.961900308 +0000
+++ new-output.txt 2025-12-05 16:25:22.658918282 +0000
@@ -3,22 +3,20 @@
_directives_deprecated_library.py:36:41: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `Self@__add__`
_directives_deprecated_library.py:41:25: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int | float`
_directives_deprecated_library.py:45:24: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `str`
-aliases_explicit.py:41:24: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[str, str]`?
aliases_explicit.py:57:5: error[type-assertion-failure] Type `(int, str, str, /) -> None` does not match asserted type `Unknown`
-aliases_explicit.py:60:5: error[type-assertion-failure] Type `(...) -> None` does not match asserted type `@Todo(Callable[..] specialized with ParamSpec)`
aliases_explicit.py:67:24: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
aliases_explicit.py:68:24: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
aliases_explicit.py:69:29: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
aliases_explicit.py:70:29: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
+aliases_explicit.py:71:24: error[invalid-type-arguments] Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`
aliases_explicit.py:101:6: error[call-non-callable] Object of type `UnionType` is not callable
aliases_explicit.py:102:20: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
-aliases_implicit.py:54:24: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[str, str]`?
aliases_implicit.py:68:5: error[type-assertion-failure] Type `(int, str, str, /) -> None` does not match asserted type `Unknown`
-aliases_implicit.py:72:5: error[type-assertion-failure] Type `(...) -> None` does not match asserted type `@Todo(Callable[..] specialized with ParamSpec)`
aliases_implicit.py:76:24: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
aliases_implicit.py:77:24: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
aliases_implicit.py:78:29: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
aliases_implicit.py:79:29: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
+aliases_implicit.py:80:24: error[invalid-type-arguments] Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`
aliases_implicit.py:81:25: error[invalid-type-arguments] Type `str` is not assignable to upper bound `int | float` of type variable `TFloat@GoodTypeAlias12`
aliases_implicit.py:107:9: error[invalid-type-form] Variable of type `list[Unknown | <class 'int'> | <class 'str'>]` is not allowed in a type expression
aliases_implicit.py:108:9: error[invalid-type-form] Variable of type `tuple[tuple[<class 'int'>, <class 'str'>]]` is not allowed in a type expression
@@ -44,7 +42,7 @@
aliases_newtype.py:61:38: error[invalid-newtype] invalid base for `typing.NewType`: type `TD1`
aliases_newtype.py:63:15: error[invalid-newtype] Wrong number of arguments in `NewType` creation, expected 2, found 3
aliases_newtype.py:65:38: error[invalid-newtype] invalid base for `typing.NewType`: type `Any`
-aliases_type_statement.py:10:35: error[invalid-type-arguments] Too many type arguments: expected 1, got 3
+aliases_type_statement.py:10:52: error[invalid-type-arguments] Too many type arguments: expected 2, got 3
aliases_type_statement.py:17:1: error[unresolved-attribute] Object of type `typing.TypeAliasType` has no attribute `bit_count`
aliases_type_statement.py:19:1: error[call-non-callable] Object of type `TypeAliasType` is not callable
aliases_type_statement.py:23:7: error[unresolved-attribute] Object of type `typing.TypeAliasType` has no attribute `other_attrib`
@@ -65,14 +63,8 @@
aliases_type_statement.py:47:23: error[invalid-type-form] Int literals are not allowed in this context in a type expression
aliases_type_statement.py:48:23: error[invalid-type-form] Boolean operations are not allowed in type expressions
aliases_type_statement.py:49:23: error[fstring-type-annotation] Type expressions cannot use f-strings
-aliases_type_statement.py:75:107: error[invalid-type-arguments] Too many type arguments: expected 2, got 3
aliases_type_statement.py:77:27: error[invalid-type-arguments] Type `str` is not assignable to upper bound `int` of type variable `S@RecursiveTypeAlias2`
-aliases_type_statement.py:77:37: error[invalid-type-arguments] Too many type arguments: expected 2, got 3
-aliases_type_statement.py:78:37: error[invalid-type-arguments] Too many type arguments: expected 2, got 3
aliases_type_statement.py:79:32: error[invalid-type-arguments] Type `int` is not assignable to upper bound `str` of type variable `T@RecursiveTypeAlias2`
-aliases_type_statement.py:79:37: error[invalid-type-arguments] Too many type arguments: expected 2, got 3
-aliases_type_statement.py:80:37: error[invalid-type-arguments] Too many type arguments: expected 2, got 3
-aliases_type_statement.py:80:37: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?
aliases_type_statement.py:82:1: error[cyclic-type-alias-definition] Cyclic definition of `RecursiveTypeAlias3`
aliases_type_statement.py:88:1: error[cyclic-type-alias-definition] Cyclic definition of `RecursiveTypeAlias6`
aliases_type_statement.py:89:1: error[cyclic-type-alias-definition] Cyclic definition of `RecursiveTypeAlias7`
@@ -158,7 +150,7 @@
callables_protocol.py:169:7: error[invalid-assignment] Object of type `def cb8_bad1(x: int) -> Any` is not assignable to `Proto8`
callables_protocol.py:186:5: error[invalid-assignment] Object of type `Literal["str"]` is not assignable to attribute `other_attribute` of type `int`
callables_protocol.py:187:5: error[unresolved-attribute] Unresolved attribute `xxx` on type `Proto9[P@decorator1, R@decorator1]`.
-callables_protocol.py:197:7: error[unresolved-attribute] Object of type `Proto9[Unknown, Unknown]` has no attribute `other_attribute2`
+callables_protocol.py:197:7: error[unresolved-attribute] Object of type `Proto9[(x: int), Unknown] | Proto9[Divergent, Unknown]` has no attribute `other_attribute2`
callables_protocol.py:238:8: error[invalid-assignment] Object of type `def cb11_bad1(x: int, y: str, /) -> Any` is not assignable to `Proto11`
callables_protocol.py:260:8: error[invalid-assignment] Object of type `def cb12_bad1(*args: Any, *, kwarg0: Any) -> None` is not assignable to `Proto12`
callables_protocol.py:284:27: error[invalid-assignment] Object of type `def cb13_no_default(path: str) -> str` is not assignable to `Proto13_Default`
@@ -244,29 +236,41 @@
constructors_call_type.py:59:9: error[too-many-positional-arguments] Too many positional arguments to bound method `__init__`: expected 1, got 2
constructors_call_type.py:81:5: error[missing-argument] No argument provided for required parameter `y` of function `__new__`
constructors_call_type.py:82:12: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `str`, found `Literal[2]`
-constructors_callable.py:36:13: info[revealed-type] Revealed type: `(...) -> Unknown`
+constructors_callable.py:36:13: info[revealed-type] Revealed type: `(x: int) -> Unknown`
constructors_callable.py:37:1: error[type-assertion-failure] Type `Class1` does not match asserted type `Unknown`
-constructors_callable.py:49:13: info[revealed-type] Revealed type: `(...) -> Unknown`
+constructors_callable.py:38:1: error[missing-argument] No argument provided for required parameter `x`
+constructors_callable.py:39:1: error[missing-argument] No argument provided for required parameter `x`
+constructors_callable.py:39:4: error[unknown-argument] Argument `y` does not match any known parameter
+constructors_callable.py:49:13: info[revealed-type] Revealed type: `() -> Unknown`
constructors_callable.py:50:1: error[type-assertion-failure] Type `Class2` does not match asserted type `Unknown`
+constructors_callable.py:51:4: error[too-many-positional-arguments] Too many positional arguments: expected 0, got 1
constructors_callable.py:57:42: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `Self@__new__`
constructors_callable.py:63:13: info[revealed-type] Revealed type: `(...) -> Unknown`
constructors_callable.py:64:1: error[type-assertion-failure] Type `Class3` does not match asserted type `Unknown`
constructors_callable.py:73:33: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
-constructors_callable.py:77:13: info[revealed-type] Revealed type: `(...) -> Unknown`
+constructors_callable.py:77:13: info[revealed-type] Revealed type: `(x: int) -> Unknown`
constructors_callable.py:78:1: error[type-assertion-failure] Type `int` does not match asserted type `Unknown`
+constructors_callable.py:79:1: error[missing-argument] No argument provided for required parameter `x`
+constructors_callable.py:80:1: error[missing-argument] No argument provided for required parameter `x`
+constructors_callable.py:80:4: error[unknown-argument] Argument `y` does not match any known parameter
constructors_callable.py:97:13: info[revealed-type] Revealed type: `(...) -> Unknown`
constructors_callable.py:100:5: error[type-assertion-failure] Type `Never` does not match asserted type `Unknown`
constructors_callable.py:105:5: error[type-assertion-failure] Type `Never` does not match asserted type `Unknown`
-constructors_callable.py:125:13: info[revealed-type] Revealed type: `(...) -> Unknown`
+constructors_callable.py:125:13: info[revealed-type] Revealed type: `() -> Unknown`
constructors_callable.py:126:1: error[type-assertion-failure] Type `Class6Proxy` does not match asserted type `Unknown`
+constructors_callable.py:127:4: error[too-many-positional-arguments] Too many positional arguments: expected 0, got 1
constructors_callable.py:142:13: info[revealed-type] Revealed type: `(...) -> Unknown`
-constructors_callable.py:162:5: info[revealed-type] Revealed type: `(...) -> Unknown`
+constructors_callable.py:162:5: info[revealed-type] Revealed type: `Overload[(x: int) -> Unknown, (x: str) -> Unknown]`
constructors_callable.py:164:1: error[type-assertion-failure] Type `Class7[int]` does not match asserted type `Unknown`
constructors_callable.py:165:1: error[type-assertion-failure] Type `Class7[str]` does not match asserted type `Unknown`
-constructors_callable.py:182:13: info[revealed-type] Revealed type: `(...) -> Unknown`
+constructors_callable.py:182:13: info[revealed-type] Revealed type: `(x: list[Unknown], y: list[Unknown]) -> Unknown`
constructors_callable.py:183:1: error[type-assertion-failure] Type `Class8[str]` does not match asserted type `Unknown`
-constructors_callable.py:193:13: info[revealed-type] Revealed type: `(...) -> Unknown`
+constructors_callable.py:193:13: info[revealed-type] Revealed type: `(x: list[T@__init__], y: list[T@__init__]) -> Unknown`
constructors_callable.py:194:1: error[type-assertion-failure] Type `Class9` does not match asserted type `Unknown`
+constructors_callable.py:194:16: error[invalid-argument-type] Argument is incorrect: Expected `list[T@__init__]`, found `list[Unknown | str]`
+constructors_callable.py:194:22: error[invalid-argument-type] Argument is incorrect: Expected `list[T@__init__]`, found `list[Unknown | str]`
+constructors_callable.py:195:4: error[invalid-argument-type] Argument is incorrect: Expected `list[T@__init__]`, found `list[Unknown | int]`
+constructors_callable.py:195:9: error[invalid-argument-type] Argument is incorrect: Expected `list[T@__init__]`, found `list[Unknown | str]`
dataclasses_descriptors.py:23:62: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int | Desc1`
dataclasses_descriptors.py:50:63: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `list[T@Desc2] | T@Desc2`
dataclasses_descriptors.py:66:1: error[type-assertion-failure] Type `int` does not match asserted type `int | Desc2[int]`
@@ -403,7 +407,7 @@
enums_member_values.py:96:1: error[type-assertion-failure] Type `int` does not match asserted type `Unknown`
enums_members.py:82:1: error[type-assertion-failure] Type `Unknown` does not match asserted type `Unknown | ((x) -> Unknown)`
enums_members.py:82:37: error[invalid-type-form] Type arguments for `Literal` must be `None`, a literal value (int, bool, str, or bytes), or an enum member
-enums_members.py:83:1: error[type-assertion-failure] Type `Unknown` does not match asserted type `Unknown | ((...) -> Unknown)`
+enums_members.py:83:1: error[type-assertion-failure] Type `Unknown` does not match asserted type `Unknown | ((x: int) -> Unknown)`
enums_members.py:83:37: error[invalid-type-form] Type arguments for `Literal` must be `None`, a literal value (int, bool, str, or bytes), or an enum member
enums_members.py:84:1: error[type-assertion-failure] Type `Unknown` does not match asserted type `property`
enums_members.py:84:35: error[invalid-type-form] Type arguments for `Literal` must be `None`, a literal value (int, bool, str, or bytes), or an enum member
@@ -446,11 +450,7 @@
generics_defaults.py:55:1: error[type-assertion-failure] Type `type[AllTheDefaults[int, int | float | complex, str, int, bool]]` does not match asserted type `<class 'AllTheDefaults[int, int | float | complex, str, int, bool]'>`
generics_defaults.py:59:1: error[type-assertion-failure] Type `type[AllTheDefaults[int, int | float | complex, str, int, bool]]` does not match asserted type `<class 'AllTheDefaults[int, int | float | complex, str, int, bool]'>`
generics_defaults.py:63:1: error[type-assertion-failure] Type `type[AllTheDefaults[int, int | float | complex, str, int, bool]]` does not match asserted type `<class 'AllTheDefaults[int, int | float | complex, str, int, bool]'>`
-generics_defaults.py:79:1: error[type-assertion-failure] Type `type[Class_ParamSpec[Unknown]]` does not match asserted type `<class 'Class_ParamSpec'>`
-generics_defaults.py:79:56: error[invalid-type-arguments] Too many type arguments to class `Class_ParamSpec`: expected between 0 and 1, got 2
-generics_defaults.py:80:53: error[invalid-type-arguments] Too many type arguments to class `Class_ParamSpec`: expected between 0 and 1, got 2
-generics_defaults.py:81:29: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[bool, bool]`?
-generics_defaults.py:81:68: error[invalid-type-arguments] Too many type arguments to class `Class_ParamSpec`: expected between 0 and 1, got 2
+generics_defaults.py:79:1: error[type-assertion-failure] Type `type[Class_ParamSpec[(str, int, /)]]` does not match asserted type `<class 'Class_ParamSpec'>`
generics_defaults.py:94:1: error[type-assertion-failure] Type `@Todo(specialized non-generic class)` does not match asserted type `<class 'Class_TypeVarTuple'>`
generics_defaults.py:95:1: error[type-assertion-failure] Type `@Todo(specialized non-generic class)` does not match asserted type `Class_TypeVarTuple`
generics_defaults.py:127:32: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `T4@func1`
@@ -468,32 +468,42 @@
generics_paramspec_basic.py:10:1: error[invalid-paramspec] The name of a `ParamSpec` (`NotIt`) must match the name of the variable it is assigned to (`WrongName`)
generics_paramspec_basic.py:23:20: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `P@func1`
generics_paramspec_basic.py:27:38: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
-generics_paramspec_components.py:49:20: error[invalid-argument-type] Argument expression after ** must be a mapping type: Found `tuple[@Todo(ParamSpecArgs / ParamSpecKwargs), ...]`
+generics_paramspec_components.py:17:25: error[invalid-type-form] `P.kwargs` is valid only in `**kwargs` annotation: Did you mean `P.args`?
+generics_paramspec_components.py:17:45: error[invalid-type-form] `P.args` is valid only in `*args` annotation: Did you mean `P.kwargs`?
+generics_paramspec_components.py:23:46: error[invalid-type-form] `P.args` is valid only in `*args` annotation: Did you mean `P.kwargs`?
+generics_paramspec_components.py:49:11: error[invalid-argument-type] Argument is incorrect: Expected `P@decorator.args`, found `P@decorator.kwargs`
+generics_paramspec_components.py:49:20: error[invalid-argument-type] Argument is incorrect: Expected `P@decorator.kwargs`, found `P@decorator.args`
+generics_paramspec_components.py:51:11: error[invalid-argument-type] Argument is incorrect: Expected `P@decorator.args`, found `Literal[1]`
+generics_paramspec_components.py:83:18: error[invalid-argument-type] Argument to function `foo` is incorrect: Expected `int`, found `(...)`
generics_paramspec_components.py:83:18: error[parameter-already-assigned] Multiple values provided for parameter 1 (`x`) of function `foo`
-generics_paramspec_semantics.py:13:56: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `(...) -> str`
+generics_paramspec_components.py:98:7: error[invalid-argument-type] Argument to function `twice` is incorrect: Expected `int`, found `Literal["A"]`
+generics_paramspec_components.py:98:20: error[invalid-argument-type] Argument to function `twice` is incorrect: Expected `str`, found `Literal[1]`
+generics_paramspec_semantics.py:13:56: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `(**P@changes_return_type_to_str) -> str`
generics_paramspec_semantics.py:17:40: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
-generics_paramspec_semantics.py:22:1: error[type-assertion-failure] Type `(str, bool, /) -> str` does not match asserted type `(...) -> str`
-generics_paramspec_semantics.py:30:56: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `(...) -> bool`
+generics_paramspec_semantics.py:26:4: error[positional-only-parameter-as-kwarg] Positional-only parameter 1 (`a`) passed as keyword argument
+generics_paramspec_semantics.py:26:11: error[positional-only-parameter-as-kwarg] Positional-only parameter 2 (`b`) passed as keyword argument
+generics_paramspec_semantics.py:27:9: error[invalid-argument-type] Argument is incorrect: Expected `bool`, found `Literal["A"]`
+generics_paramspec_semantics.py:30:56: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `(**P@func1) -> bool`
generics_paramspec_semantics.py:34:28: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
generics_paramspec_semantics.py:38:28: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
+generics_paramspec_semantics.py:46:17: error[invalid-argument-type] Argument to function `func1` is incorrect: Expected `(x: int, y: str) -> int`, found `def y_x(y: int, x: str) -> int`
generics_paramspec_semantics.py:53:34: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
generics_paramspec_semantics.py:57:34: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
+generics_paramspec_semantics.py:61:23: error[invalid-argument-type] Argument to function `func1` is incorrect: Expected `(*, x: int) -> int`, found `def keyword_only_y(*, y: int) -> int`
generics_paramspec_semantics.py:76:30: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `str`
-generics_paramspec_semantics.py:82:28: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `list[int]`?
-generics_paramspec_semantics.py:84:5: error[type-assertion-failure] Type `(int, /) -> str` does not match asserted type `(...) -> str`
generics_paramspec_semantics.py:87:33: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
generics_paramspec_semantics.py:91:33: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `(...) -> bool`
-generics_paramspec_semantics.py:101:54: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `(...) -> bool`
+generics_paramspec_semantics.py:101:54: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `(**P@remove) -> bool`
generics_paramspec_semantics.py:113:6: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `(...) -> bool`
generics_paramspec_semantics.py:128:20: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
generics_paramspec_semantics.py:133:23: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
generics_paramspec_semantics.py:138:29: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
generics_paramspec_semantics.py:143:25: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
-generics_paramspec_specialization.py:32:27: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, bool]`?
-generics_paramspec_specialization.py:40:27: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?
-generics_paramspec_specialization.py:40:31: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?
-generics_paramspec_specialization.py:52:22: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, str, bool]`?
-generics_paramspec_specialization.py:58:27: error[invalid-type-arguments] Too many type arguments to class `ClassC`: expected 1, got 3
+generics_paramspec_specialization.py:44:27: error[invalid-type-arguments] Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`
+generics_paramspec_specialization.py:54:9: error[invalid-argument-type] Argument is incorrect: Expected `int`, found `Literal[""]`
+generics_paramspec_specialization.py:55:16: error[invalid-argument-type] Argument is incorrect: Expected `bool`, found `Literal[""]`
+generics_paramspec_specialization.py:60:9: error[invalid-argument-type] Argument is incorrect: Expected `int`, found `Literal[""]`
+generics_paramspec_specialization.py:61:16: error[invalid-argument-type] Argument is incorrect: Expected `bool`, found `Literal[""]`
generics_scoping.py:14:1: error[type-assertion-failure] Type `int` does not match asserted type `Literal[1]`
generics_scoping.py:15:1: error[type-assertion-failure] Type `str` does not match asserted type `Literal["a"]`
generics_scoping.py:42:1: error[type-assertion-failure] Type `str` does not match asserted type `Literal["abc"]`
@@ -575,9 +585,9 @@
generics_syntax_infer_variance.py:165:45: error[invalid-assignment] Object of type `ShouldBeContravariant1[int]` is not assignable to `ShouldBeContravariant1[int | float]`
generics_syntax_infer_variance.py:166:43: error[invalid-assignment] Object of type `ShouldBeContravariant1[int | float]` is not assignable to `ShouldBeContravariant1[int]`
generics_syntax_scoping.py:35:7: error[unresolved-reference] Name `T` used when not defined
-generics_syntax_scoping.py:40:23: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `((...) -> R@decorator1, /) -> (...) -> R@decorator1`
+generics_syntax_scoping.py:40:23: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `((**P@decorator1) -> R@decorator1, /) -> (**P@decorator1) -> R@decorator1`
generics_syntax_scoping.py:44:17: error[unresolved-reference] Name `T` used when not defined
-generics_syntax_scoping.py:81:35: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `((...) -> R@decorator2, /) -> (...) -> R@decorator2`
+generics_syntax_scoping.py:81:35: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `((**P@decorator2) -> R@decorator2, /) -> (**P@decorator2) -> R@decorator2`
generics_syntax_scoping.py:113:9: error[type-assertion-failure] Type `str` does not match asserted type `Literal[""]`
generics_syntax_scoping.py:116:13: error[type-assertion-failure] Type `TypeVar` does not match asserted type `typing.TypeVar`
generics_syntax_scoping.py:121:9: error[type-assertion-failure] Type `int | float | complex` does not match asserted type `complex`
@@ -1021,4 +1031,4 @@
typeddicts_usage.py:28:17: error[missing-typed-dict-key] Missing required key 'name' in TypedDict `Movie` constructor
typeddicts_usage.py:28:18: error[invalid-key] Unknown key "title" for TypedDict `Movie`: Unknown key "title"
typeddicts_usage.py:40:24: error[invalid-type-form] The special form `typing.TypedDict` is not allowed in type expressions
-Found 1023 diagnostics
+Found 1033 diagnostics
|
|
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
## Summary This PR updates the explicit specialization logic to avoid using the call machinery. Previously, the logic would use the call machinery by converting the list of type variables into a `Binding` with a single `Signature` where all the type variables are positional-only parameters with bounds and constraints as the annotated type and the default type as the default parameter value. This has the advantage that it doesn't need to implement any specific logic but the disadvantages are subpar diagnostic messages as it would use the ones specific to a function call. But, an important disadvantage is that the kind of type variable is lost in this translation which becomes important in #21445 where a `ParamSpec` can specialize into a list of types which is provided using list literal. For example, ```py class Foo[T, **P]: ... Foo[int, [int, str]] ``` This PR converts the logic to use a simple loop using `zip_longest` as all type variables and their corresponding type argument maps on a 1-1 basis. They cannot be specified using keyword argument either e.g., `dict[_VT=str, _KT=int]` is invalid. This PR also makes an initial attempt to improve the diagnostic message to specifically target the specialization part by using words like "type argument" instead of just "argument" and including information like the type variable, bounds, and constraints. Further improvements can be made by highlighting the type variable definition or the bounds / constraints as a sub-diagnostic but I'm going to leave that as a follow-up. ## Test Plan Update messages in existing test cases.
AlexWaygood
left a comment
There was a problem hiding this comment.
Just a review of the tests so far, which are overall fantastic. So excited to see how powerful this makes some of our type inference!!
crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md
Outdated
Show resolved
Hide resolved
crates/ty_python_semantic/resources/mdtest/generics/legacy/paramspec.md
Outdated
Show resolved
Hide resolved
crates/ty_python_semantic/resources/mdtest/generics/legacy/paramspec.md
Outdated
Show resolved
Hide resolved
crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md
Outdated
Show resolved
Hide resolved
dcreager
left a comment
There was a problem hiding this comment.
Some initial thoughts, still need to look through call/bind.rs in more detail
| _ => Type::unknown(), | ||
| }; | ||
|
|
||
| let value_type = match argument_type { |
There was a problem hiding this comment.
Instead of repeating the match statement here, you might consider adding this logic as a method on VariadicArgumentType
There was a problem hiding this comment.
I'm not sure how would that work. The VariadicArgumentType is specific to *args but this is handling the **kwargs (keyword variadic). Let me see if I can think of a way to de-duplicate this, currently it's used in match_variadic_argument, match_keyword_variadic and check_keyword_variadic_argument_type but they have slightly different semantics. I think the keyword variadic handling can possibly be clubbed together.
There was a problem hiding this comment.
I'm not sure how would that work. The
VariadicArgumentTypeis specific to*argsbut this is handling the**kwargs(keyword variadic).
Ah I missed that distinction! Yeah you could theoretically have both VariadicArgumentType and KeywordVariadicArgumentType, which would possibly look very similar to each other.
In general I like avoiding repeating these kinds of tests since it adds cognitive load to keep them in sync. But I'm fine if you decide not to do this, or mark it as a TODO. It's not worth doing if it will be a heavy lift — or at least not in this PR.
There was a problem hiding this comment.
Makes sense, I'll try to take a stab at it before landing this PR but otherwise will prefer to do it as a follow-up.
There was a problem hiding this comment.
I've noted this down, will do it as a follow-up instead.
| provided_ty: argument_type, | ||
| }); | ||
| return; | ||
| let value_type = match argument_type { |
There was a problem hiding this comment.
Same as above — ideally we wouldn't have to repeat this match statement here, and you could add this as a method on VariadicArgumentType. Though there you don't have access to that type, so in this case it might not be worth the refactoring churn
| // N.B. We cannot represent a heterogeneous list of types in our type system, so we | ||
| // use a heterogeneous tuple type to represent the list of types instead. |
There was a problem hiding this comment.
We have considered adding a new Type variant to handle heterogeneous lists. I don't know if there's an open issue for it. But if we ever do that, it will probably lean heavily on TupleSpec, so 👍 to doing it this way for now, since that will make it most likely that this logic would carry over easily.
| // | ||
| // class Foo[**P]: ... | ||
| // | ||
| // Foo[ParamSpec] # P: (ParamSpec, /) |
| (Type::Callable(formal_callable), _) => { | ||
| if let Some(actual_callable) = actual | ||
| .try_upcast_to_callable(self.db) | ||
| .and_then(CallableTypes::exactly_one) | ||
| { |
There was a problem hiding this comment.
This is where the main conflict with #21551 will be. No suggested changes for this PR though!
To write down my plans for the merge conflict: #21551 adds a new ConstraintSetAssignability typing relation, and this logic will move over to Type::has_relation_to_impl, and will define what constraint set we create to describe when a callable lhs/actual is assignable to a ParamSpec callable rhs/formal.
* origin/main: [ty] Allow `tuple[Any, ...]` to assign to `tuple[int, *tuple[int, ...]]` (#21803) [ty] Support renaming import aliases (#21792) [ty] Add redeclaration LSP tests (#21812) [ty] more detailed description of "Size limit on unions of literals" in mdtest (#21804) [ty] Complete support for `ParamSpec` (#21445) [ty] Update benchmark dependencies (#21815)
* origin/main: [ty] Add test case for fixed panic (#21832) [ty] Avoid double-analyzing tuple in `Final` subscript (#21828) [flake8-bandit] Fix false positive when using non-standard `CSafeLoader` path (S506). (#21830) Add minimal-size build profile (#21826) [ty] Allow `tuple[Any, ...]` to assign to `tuple[int, *tuple[int, ...]]` (#21803) [ty] Support renaming import aliases (#21792) [ty] Add redeclaration LSP tests (#21812) [ty] more detailed description of "Size limit on unions of literals" in mdtest (#21804) [ty] Complete support for `ParamSpec` (#21445) [ty] Update benchmark dependencies (#21815)
Summary
Closes: astral-sh/ty#157
This PR adds support for the following capabilities involving a
ParamSpectype variable:P.argsandP.kwargsin the type systemPto create a type mappingPagainst the stored parametersThe value of a
ParamSpectype variable is being represented usingCallableTypewith aCallableTypeKind::ParamSpecValuevariant. ThisCallableTypeKindis expanded from the existingis_function_likeboolean flag. Anenumis used as these variants are mutually exclusive.For context, an initial iteration made an attempt to expand the
Specializationto useTypeOrParametersenum that represents that a type variable can specialize into either aTypeorParametersbut that increased the complexity of the code as all downstream usages would need to handle both the variants appropriately. Additionally, we'd have also need to establish an invariant that a regular type variable always maps to aTypewhile a paramspec type variable always maps to aParameters.I've intentionally left out checking and raising diagnostics when the
ParamSpectype variable and it's components are not being used correctly to avoid scope increase and it can easily be done as a follow-up. This would also include the scoping rules which I don't think a regular type variable implements either.Test Plan
Add new mdtest cases and update existing test cases.
Ran this branch on pyx, no new diagnostics.
Ecosystem analysis
There's a case where in an annotated assignment like:
The type of
valueis a callable and it has a paramspec that's bound tovalue,CustomTypeis a type alias that's a callable andPthat's used in it's specialization is bound toanother. Now, ty infers the type oftargetsame asvalueand does not use the declared typeCustomType[P]. This is the assignment that I'm referring to which then leads to error in downstream usage. Pyright and mypy does seem to use the declared type.There are multiple diagnostics in
dd-trace-pythat requires support forcls.I'm seeing
Divergenttype for an example like whichI'm not sure why, I'll look into it tomorrowis because of a cycle as mentioned in astral-sh/ty#1729 (comment):I
need to look into why are the parameters not being specialized through multiple decorators in the following codethink this is also because of the cycle mentioned in astral-sh/ty#1729 (comment) and the fact that we don't supportstaticmethodproperly:There's some issue related to
Protocolthat are generic over aParamSpecinstarlettewhich might be related to astral-sh/ty#1635 but I'm not sure. Here's a minimal example to reproduce:Code snippet:
Conformance analysis
Requires return type inference i.e., #21551
I might need to look into why this is happening...
which is on the following code
It's occurring because there's no equivalence relationship defined between
ClassLiteralandKnownInstanceType::TypeGenericAliaswhich is what these types are.Everything else looks good to me!