Skip to content

Commit 1bb9bb8

Browse files
committed
conformance: fix generics_defaults to flag ParamSpec-after-TypeVarTuple without TVT default
The conformance test for `class Foo6(Generic[*Ts, P])` was missing an `# E` marker. The spec only permits a defaulted `ParamSpec` to follow a `TypeVarTuple` when the `TypeVarTuple` **also has a default**; in the test `Ts` had no default, so the class is ambiguous (exactly like the `TypeVar`-after-`TypeVarTuple` rule) and raises `TypeError` at runtime. Changes: - Add `TsD = TypeVarTuple("TsD", default=Unpack[tuple[int, str]])` for the valid case. - Mark `class Foo6(Generic[*Ts, P])` as `# E` (Ts has no default). - Add `class Foo6b(Generic[*TsD, P])` as the spec-valid case (both `TsD` and `P` have defaults) with corresponding assertions. - Clarify the spec prose to state the "TypeVarTuple **with a default**" condition explicitly and show a counter-example. - Update all six checker result TOMLs. Fixes: #2211
1 parent 7c607df commit 1bb9bb8

9 files changed

Lines changed: 60 additions & 40 deletions

File tree

conformance/results/mypy/generics_defaults.toml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@ generics_defaults.py:139: error: Expression is of type "tuple[*tuple[str, int],
1010
generics_defaults.py:152: error: TypeVar default must be a subtype of the bound type [misc]
1111
generics_defaults.py:159: error: TypeVar default must be one of the constraint types [misc]
1212
generics_defaults.py:177: error: Expression is of type "int", not "Any" [assert-type]
13-
generics_defaults.py:203: error: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "str" [valid-type]
14-
generics_defaults.py:204: error: Expression is of type "tuple[Any, ...]", not "tuple[int, str]" [assert-type]
15-
generics_defaults.py:205: error: Expression is of type "def (*Any, **Any) -> None", not "Callable[[float, bool], None]" [assert-type]
13+
generics_defaults.py:211: error: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "str" [valid-type]
14+
generics_defaults.py:212: error: Expression is of type "tuple[Any, ...]", not "tuple[int, str]" [assert-type]
15+
generics_defaults.py:213: error: Expression is of type "def (*Any, **Any) -> None", not "Callable[[float, bool], None]" [assert-type]
1616
"""
1717
conformance_automated = "Fail"
1818
errors_diff = """
19-
Line 188: Expected 1 errors
19+
Line 189: Expected 1 errors
20+
Line 201: Expected 1 errors
2021
Line 139: Unexpected errors ['generics_defaults.py:139: error: Expression is of type "tuple[*tuple[str, int], ...]", not "tuple[str, int]" [assert-type]']
21-
Line 203: Unexpected errors ['generics_defaults.py:203: error: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "str" [valid-type]']
22-
Line 204: Unexpected errors ['generics_defaults.py:204: error: Expression is of type "tuple[Any, ...]", not "tuple[int, str]" [assert-type]']
23-
Line 205: Unexpected errors ['generics_defaults.py:205: error: Expression is of type "def (*Any, **Any) -> None", not "Callable[[float, bool], None]" [assert-type]']
22+
Line 211: Unexpected errors ['generics_defaults.py:211: error: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "str" [valid-type]']
23+
Line 212: Unexpected errors ['generics_defaults.py:212: error: Expression is of type "tuple[Any, ...]", not "tuple[int, str]" [assert-type]']
24+
Line 213: Unexpected errors ['generics_defaults.py:213: error: Expression is of type "def (*Any, **Any) -> None", not "Callable[[float, bool], None]" [assert-type]']
2425
"""

conformance/results/pycroscope/generics_defaults.toml

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Numerous issues; does not support TypeVarTuple and ParamSpec defaults.
44
"""
55
conformance_automated = "Fail"
66
errors_diff = """
7+
Line 201: Expected 1 errors
78
Line 33: Unexpected errors ['./generics_defaults.py:33:16: ~DefaultStrT@./generics_defaults.py.NoNonDefaults is not equivalent to str']
89
Line 34: Unexpected errors ['./generics_defaults.py:34:16: ~DefaultIntT@./generics_defaults.py.NoNonDefaults is not equivalent to int']
910
Line 73: Unexpected errors ['./generics_defaults.py:73:16: ~T1@./generics_defaults.py.AllTheDefaults is not equivalent to Any[explicit]']
@@ -14,10 +15,10 @@ Line 77: Unexpected errors ['./generics_defaults.py:77:16: ~DefaultBoolT@./gen
1415
Line 122: Unexpected errors ['./generics_defaults.py:122:16: (**__P: **DefaultP@./generics_defaults.py.Class_ParamSpec) -> None is not equivalent to (str, int, /) -> None']
1516
Line 124: Unexpected errors ['./generics_defaults.py:124:16: ./generics_defaults.py.Class_ParamSpec[AnySig()] is not equivalent to ./generics_defaults.py.Class_ParamSpec[tuple[bool, bool]]']
1617
Line 144: Unexpected errors ['./generics_defaults.py:144:60: Incompatible argument type for default: expected TypeForm[object] but got Literal[typing.Unpack[DefaultTs]] [incompatible_argument]']
17-
Line 203: Unexpected errors ["./generics_defaults.py:203:36: Invalid type annotation [<class 'bytes'>] [invalid_annotation]", './generics_defaults.py:203:17: ParamSpec specialization must use list form, Concatenate[..., P], P, or ... [invalid_annotation]']
18-
Line 204: Unexpected errors ['./generics_defaults.py:204:16: tuple[int] is not equivalent to tuple[int, str]']
19-
Line 205: Unexpected errors ['./generics_defaults.py:205:16: (...) -> None is not equivalent to (float | int, bool, /) -> None']
20-
Line 208: Unexpected errors ['./generics_defaults.py:208:16: (...) -> None is not equivalent to (bytes, /) -> None']
18+
Line 211: Unexpected errors ["./generics_defaults.py:211:38: Invalid type annotation [<class 'bytes'>] [invalid_annotation]", './generics_defaults.py:211:18: ParamSpec specialization must use list form, Concatenate[..., P], P, or ... [invalid_annotation]']
19+
Line 212: Unexpected errors ['./generics_defaults.py:212:16: tuple[int] is not equivalent to tuple[int, str]']
20+
Line 213: Unexpected errors ['./generics_defaults.py:213:16: (...) -> None is not equivalent to (float | int, bool, /) -> None']
21+
Line 216: Unexpected errors ['./generics_defaults.py:216:16: (...) -> None is not equivalent to (bytes, /) -> None']
2122
"""
2223
output = """
2324
./generics_defaults.py:24:0: non-default TypeVars cannot follow ones with defaults [invalid_type_parameter]
@@ -35,10 +36,10 @@ output = """
3536
./generics_defaults.py:152:50: TypeVar default must be assignable to its bound [incompatible_call]
3637
./generics_defaults.py:159:51: TypeVar default must be one of its constraints [incompatible_call]
3738
./generics_defaults.py:176:12: Any[generic_argument] is not equivalent to int
38-
./generics_defaults.py:188:0: TypeVars with defaults cannot follow TypeVarTuples [invalid_type_parameter]
39-
./generics_defaults.py:203:36: Invalid type annotation [<class 'bytes'>] [invalid_annotation]
40-
./generics_defaults.py:203:17: ParamSpec specialization must use list form, Concatenate[..., P], P, or ... [invalid_annotation]
41-
./generics_defaults.py:204:16: tuple[int] is not equivalent to tuple[int, str]
42-
./generics_defaults.py:205:16: (...) -> None is not equivalent to (float | int, bool, /) -> None
43-
./generics_defaults.py:208:16: (...) -> None is not equivalent to (bytes, /) -> None
39+
./generics_defaults.py:189:0: TypeVars with defaults cannot follow TypeVarTuples [invalid_type_parameter]
40+
./generics_defaults.py:211:38: Invalid type annotation [<class 'bytes'>] [invalid_annotation]
41+
./generics_defaults.py:211:18: ParamSpec specialization must use list form, Concatenate[..., P], P, or ... [invalid_annotation]
42+
./generics_defaults.py:212:16: tuple[int] is not equivalent to tuple[int, str]
43+
./generics_defaults.py:213:16: (...) -> None is not equivalent to (float | int, bool, /) -> None
44+
./generics_defaults.py:216:16: (...) -> None is not equivalent to (bytes, /) -> None
4445
"""
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
conformant = "Pass"
2-
conformance_automated = "Pass"
2+
conformance_automated = "Fail"
33
errors_diff = """
4+
Line 201: Expected 1 errors
45
"""
56
output = """
67
ERROR generics_defaults.py:24:7-31: Type parameter `T` without a default cannot follow type parameter `DefaultStrT` with a default [invalid-type-var]
78
ERROR generics_defaults.py:66:8-27: Expected 5 type arguments for `AllTheDefaults`, got 1 [bad-specialization]
89
ERROR generics_defaults.py:152:51-54: Expected default `int` of `Invalid1` to be assignable to the upper bound of `str` [invalid-type-var]
910
ERROR generics_defaults.py:159:52-55: Expected default `int` of `Invalid2` to be one of the following constraints: `float`, `str` [invalid-type-var]
1011
ERROR generics_defaults.py:177:12-27: assert_type(int, Any) failed [assert-type]
11-
ERROR generics_defaults.py:188:7-11: TypeVar `T5` with a default cannot follow TypeVarTuple `Ts` [invalid-type-var]
12+
ERROR generics_defaults.py:189:7-11: TypeVar `T5` with a default cannot follow TypeVarTuple `Ts` [invalid-type-var]
1213
"""

conformance/results/pyright/generics_defaults.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ generics_defaults.py:66:23 - error: Too few type arguments provided for "AllTheD
55
generics_defaults.py:152:51 - error: TypeVar default type must be a subtype of the bound type (reportGeneralTypeIssues)
66
generics_defaults.py:159:52 - error: TypeVar default type must be one of the constrained types (reportGeneralTypeIssues)
77
generics_defaults.py:177:13 - error: "assert_type" mismatch: expected "Any" but received "int" (reportAssertTypeFailure)
8-
generics_defaults.py:188:7 - error: TypeVar "T5" has a default value and cannot follow TypeVarTuple "Ts" (reportGeneralTypeIssues)
8+
generics_defaults.py:189:7 - error: TypeVar "T5" has a default value and cannot follow TypeVarTuple "Ts" (reportGeneralTypeIssues)
99
"""
10-
conformance_automated = "Pass"
10+
conformance_automated = "Fail"
1111
errors_diff = """
12+
Line 201: Expected 1 errors
1213
"""
1314
ignore_errors = ["Access to generic instance variable through class is ambiguous"]

conformance/results/results.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ <h3>Python Type System Conformance Test Results</h3>
304304
<tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;generics_defaults</th>
305305
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not detect a TypeVar with a default used after a TypeVarTuple.</p><p>Does not fully support defaults on TypeVarTuple and ParamSpec.</p></span></div></th>
306306
<th class="column col2 conformant">Pass</th>
307-
<th class="column col2 conformant">Pass</th>
307+
<th class="column col2 not-conformant">Unknown</th>
308308
<th class="column col2 conformant">Pass</th>
309309
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Numerous issues; does not support TypeVarTuple and ParamSpec defaults.</p></span></div></th>
310310
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not forbid a `TypeVar` immediately following a `TypeVarTuple` in a parameter list from having a default.</p><p>Does not support `TypeVarTuple`.</p></span></div></th>

conformance/results/ty/generics_defaults.toml

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ Does not forbid a `TypeVar` immediately following a `TypeVarTuple` in a paramete
55
Does not support `TypeVarTuple`.
66
"""
77
errors_diff = """
8-
Line 188: Expected 1 errors
8+
Line 189: Expected 1 errors
9+
Line 201: Expected 1 errors
910
Line 139: Unexpected errors ['generics_defaults.py:139:5: error[type-assertion-failure] Type `tuple[@Todo(TypeVarTuple), ...]` does not match asserted type `tuple[str, int]`']
1011
Line 140: Unexpected errors ['generics_defaults.py:140:5: error[type-assertion-failure] Type `Class_TypeVarTuple` does not match asserted type `@Todo`']
11-
Line 200: Unexpected errors ['generics_defaults.py:200:17: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`']
12-
Line 204: Unexpected errors ['generics_defaults.py:204:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `tuple[int, str]`']
13-
Line 205: Unexpected errors ['generics_defaults.py:205:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `(int | float, bool, /) -> None`']
14-
Line 207: Unexpected errors ['generics_defaults.py:207:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `tuple[int, str]`']
15-
Line 208: Unexpected errors ['generics_defaults.py:208:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `(bytes, /) -> None`']
12+
Line 203: Unexpected errors ['generics_defaults.py:203:17: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`']
13+
Line 208: Unexpected errors ['generics_defaults.py:208:17: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`']
14+
Line 212: Unexpected errors ['generics_defaults.py:212:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `tuple[int, str]`']
15+
Line 213: Unexpected errors ['generics_defaults.py:213:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `(int | float, bool, /) -> None`']
16+
Line 215: Unexpected errors ['generics_defaults.py:215:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `tuple[int, str]`']
17+
Line 216: Unexpected errors ['generics_defaults.py:216:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `(bytes, /) -> None`']
1618
"""
1719
output = """
1820
generics_defaults.py:24:40: error[invalid-generic-class] Type parameter `T` without a default cannot follow earlier parameter `DefaultStrT` with a default
@@ -22,9 +24,10 @@ generics_defaults.py:140:5: error[type-assertion-failure] Type `Class_TypeVarTup
2224
generics_defaults.py:152:51: error[invalid-type-variable-default] TypeVar default is not assignable to the TypeVar's upper bound
2325
generics_defaults.py:159:52: error[invalid-type-variable-default] TypeVar default is inconsistent with the TypeVar's constraints: `int` is not one of the constraints of `Invalid2`
2426
generics_defaults.py:177:1: error[type-assertion-failure] Type `int` does not match asserted type `Any`
25-
generics_defaults.py:200:17: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`
26-
generics_defaults.py:204:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `tuple[int, str]`
27-
generics_defaults.py:205:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `(int | float, bool, /) -> None`
28-
generics_defaults.py:207:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `tuple[int, str]`
29-
generics_defaults.py:208:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `(bytes, /) -> None`
27+
generics_defaults.py:203:17: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`
28+
generics_defaults.py:208:17: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`
29+
generics_defaults.py:212:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `tuple[int, str]`
30+
generics_defaults.py:213:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `(int | float, bool, /) -> None`
31+
generics_defaults.py:215:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `tuple[int, str]`
32+
generics_defaults.py:216:5: error[type-assertion-failure] Type `@Todo` does not match asserted type `(bytes, /) -> None`
3033
"""
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
conformance_automated = "Pass"
1+
conformance_automated = "Fail"
22
errors_diff = """
3+
Line 201: Expected 1 errors
34
"""
45
output = """
56
generics_defaults.py:24: error: "T" cannot appear after "DefaultStrT" in type parameter list because it has no default type [misc]
67
generics_defaults.py:66: error: "AllTheDefaults" expects between 2 and 5 type arguments, but 1 given [type-arg]
78
generics_defaults.py:152: error: TypeVar default must be a subtype of the bound type [misc]
89
generics_defaults.py:159: error: TypeVar default must be one of the constraint types [misc]
910
generics_defaults.py:177: error: Expression is of type "int", not "Any" [misc]
10-
generics_defaults.py:188: error: TypeVar defaults are ambiguous after a TypeVarTuple [misc]
11+
generics_defaults.py:189: error: TypeVar defaults are ambiguous after a TypeVarTuple [misc]
1112
"""

conformance/tests/generics_defaults.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,24 +183,32 @@ def func1(x: int | set[T4]) -> T4:
183183

184184
Ts = TypeVarTuple("Ts")
185185
T5 = TypeVar("T5", default=bool)
186+
TsD = TypeVarTuple("TsD", default=Unpack[tuple[int, str]])
186187

187188

188189
class Foo5(Generic[*Ts, T5]): ... # E
189190

190191

191192
# > It is allowed to have a ``ParamSpec`` with a default following a
192-
# > ``TypeVarTuple`` with a default, as there can be no ambiguity between a
193+
# > ``TypeVarTuple`` **with a default**, as there can be no ambiguity between a
193194
# > type argument for the ``ParamSpec`` and one for the ``TypeVarTuple``.
195+
# > When the ``TypeVarTuple`` has no default, a ``ParamSpec`` with a default
196+
# > following it is not allowed (same rule as for ``TypeVar``).
194197

195198
P = ParamSpec("P", default=[float, bool])
196199

197200

198-
class Foo6(Generic[*Ts, P]):
201+
class Foo6(Generic[*Ts, P]): # E: Ts has no default
199202
x: tuple[*Ts]
200203
y: Callable[P, None]
201204

202205

203-
def test_foo6(a: Foo6[int, str], b: Foo6[int, str, [bytes]]):
206+
class Foo6b(Generic[*TsD, P]): # OK: TsD has a default
207+
x: tuple[*TsD]
208+
y: Callable[P, None]
209+
210+
211+
def test_foo6b(a: Foo6b[int, str], b: Foo6b[int, str, [bytes]]):
204212
assert_type(a.x, tuple[int, str])
205213
assert_type(a.y, Callable[[float, bool], None])
206214

docs/spec/generics.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2098,8 +2098,10 @@ should be bound to the ``TypeVarTuple`` or the defaulted ``TypeVar``.
20982098
Foo[int, str, float]
20992099

21002100
It is allowed to have a ``ParamSpec`` with a default following a
2101-
``TypeVarTuple`` with a default, as there can be no ambiguity between a type argument
2102-
for the ``ParamSpec`` and one for the ``TypeVarTuple``.
2101+
``TypeVarTuple`` **with a default**, as there can be no ambiguity between a
2102+
type argument for the ``ParamSpec`` and one for the ``TypeVarTuple``.
2103+
When the ``TypeVarTuple`` has no default, a ``ParamSpec`` with a default
2104+
following it is not allowed (same rule as for ``TypeVar``).
21032105

21042106
::
21052107

@@ -2108,6 +2110,8 @@ for the ``ParamSpec`` and one for the ``TypeVarTuple``.
21082110
Foo[int, str] # Ts = (int, str), P = [float, bool]
21092111
Foo[int, str, [bytes]] # Ts = (int, str), P = [bytes]
21102112

2113+
class Bar[*Ts, **P = [float, bool]]: ... # Type checker error: Ts has no default
2114+
21112115
Binding rules
21122116
"""""""""""""
21132117

0 commit comments

Comments
 (0)