Skip to content

Commit a8bc692

Browse files
committed
Added support for ... in the last type argument of Concatenate. After much discussion (see python/cpython#30969), it has been decided to support this even though a strict reading of PEP 612 seems to exclude it. This addresses #5715.
1 parent a324a05 commit a8bc692

File tree

6 files changed

+62
-6
lines changed

6 files changed

+62
-6
lines changed

packages/pyright-internal/src/analyzer/typeEvaluator.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -6531,6 +6531,9 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
65316531

65326532
if (isParamSpec(typeArg)) {
65336533
functionType.details.paramSpec = typeArg;
6534+
} else if (isEllipsisType(typeArg)) {
6535+
FunctionType.addDefaultParameters(functionType);
6536+
functionType.details.flags |= FunctionTypeFlags.SkipArgsKwargsCompatibilityCheck;
65346537
}
65356538
} else {
65366539
FunctionType.addParameter(functionType, {
@@ -14078,6 +14081,9 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
1407814081

1407914082
if (isParamSpec(typeArg)) {
1408014083
functionType.details.paramSpec = typeArg;
14084+
} else if (isEllipsisType(typeArg)) {
14085+
FunctionType.addDefaultParameters(functionType);
14086+
functionType.details.flags |= FunctionTypeFlags.SkipArgsKwargsCompatibilityCheck;
1408114087
}
1408214088
} else {
1408314089
FunctionType.addParameter(functionType, {
@@ -14606,7 +14612,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
1460614612
} else {
1460714613
typeArgs.forEach((typeArg, index) => {
1460814614
if (index === typeArgs.length - 1) {
14609-
if (!isParamSpec(typeArg.type)) {
14615+
if (!isParamSpec(typeArg.type) && !isEllipsisType(typeArg.type)) {
1461014616
addError(Localizer.Diagnostic.concatenateParamSpecMissing(), typeArg.node);
1461114617
}
1461214618
} else {
@@ -14673,7 +14679,9 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
1467314679
typeArgs.forEach((typeArg, index) => {
1467414680
if (isEllipsisType(typeArg.type)) {
1467514681
if (!isTupleTypeParam) {
14676-
addError(Localizer.Diagnostic.ellipsisContext(), typeArg.node);
14682+
if (!allowParamSpec) {
14683+
addError(Localizer.Diagnostic.ellipsisContext(), typeArg.node);
14684+
}
1467714685
} else if (typeArgs!.length !== 2 || index !== 1) {
1467814686
addError(Localizer.Diagnostic.ellipsisSecondArg(), typeArg.node);
1467914687
} else {
@@ -19136,6 +19144,10 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
1913619144
if (index === concatTypeArgs.length - 1) {
1913719145
if (isParamSpec(typeArg)) {
1913819146
functionType.details.paramSpec = typeArg;
19147+
} else if (isEllipsisType(typeArg)) {
19148+
FunctionType.addDefaultParameters(functionType);
19149+
functionType.details.flags |=
19150+
FunctionTypeFlags.SkipArgsKwargsCompatibilityCheck;
1913919151
}
1914019152
} else {
1914119153
FunctionType.addParameter(functionType, {

packages/pyright-internal/src/localization/package.nls.en-us.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"comparisonAlwaysTrue": "Condition will always evaluate to True since the types \"{leftType}\" and \"{rightType}\" have no overlap",
6868
"comprehensionInDict": "Comprehension cannot be used with other dictionary entries",
6969
"comprehensionInSet": "Comprehension cannot be used with other set entries",
70-
"concatenateParamSpecMissing": "Last type argument for \"Concatenate\" must be a ParamSpec",
70+
"concatenateParamSpecMissing": "Last type argument for \"Concatenate\" must be a ParamSpec or \"...\"",
7171
"concatenateTypeArgsMissing": "\"Concatenate\" requires at least two type arguments",
7272
"conditionalOperandInvalid": "Invalid conditional operand of type \"{type}\"",
7373
"constantRedefinition": "\"{name}\" is constant (because it is uppercase) and cannot be redefined",

packages/pyright-internal/src/tests/samples/ellipsis1.pyi

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ _T1 = TypeVar("_T1")
88
class MyGenericClass1(Generic[_T1]):
99
pass
1010

11-
# This should generate two errors because ... cannot be used
12-
# in a Generic list and it's not a type variable.
11+
# This should generate an error because ... is not a type variable.
1312
class MyGenericClass2(Generic[_T1, ...]):
1413
pass
1514

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# This sample tests support for Concatenate with a ... type argument.
2+
3+
from typing import Callable, Concatenate
4+
5+
TA1 = Callable[Concatenate[int, ...], None]
6+
7+
8+
def func1(cb: Callable[Concatenate[int, str, ...], None]):
9+
...
10+
11+
12+
def func2(cb: TA1):
13+
...
14+
15+
16+
def cb1(x: int, y: str, z: str) -> None:
17+
...
18+
19+
20+
func1(cb1)
21+
func2(cb1)
22+
23+
24+
def cb2(x: int, y: str, *args: int, **kwargs: str) -> None:
25+
...
26+
27+
28+
func1(cb2)
29+
func2(cb2)
30+
31+
32+
def cb3(x: str, y: str) -> None:
33+
...
34+
35+
36+
# This should generate an error.
37+
func1(cb3)
38+
39+
# This should generate an error.
40+
func2(cb3)

packages/pyright-internal/src/tests/typeEvaluator3.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ test('Module2', () => {
2727
test('Ellipsis1', () => {
2828
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['ellipsis1.pyi']);
2929

30-
TestUtils.validateResults(analysisResults, 11);
30+
TestUtils.validateResults(analysisResults, 10);
3131
});
3232

3333
test('Generator1', () => {

packages/pyright-internal/src/tests/typeEvaluator4.test.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,11 @@ test('ParamSpec45', () => {
10481048
TestUtils.validateResults(results, 0);
10491049
});
10501050

1051+
test('ParamSpec46', () => {
1052+
const results = TestUtils.typeAnalyzeSampleFiles(['paramSpec46.py']);
1053+
TestUtils.validateResults(results, 2);
1054+
});
1055+
10511056
test('ClassVar1', () => {
10521057
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['classVar1.py']);
10531058

0 commit comments

Comments
 (0)