Skip to content

Commit c9e22a7

Browse files
authored
Fixed bug related to function type compatibility when the dest type contains an unpacked tuple followed by one or more positional-only parameters as in Callable[[*Ts, int], None]. This addresses #6724. (#6751)
1 parent bc72b3c commit c9e22a7

File tree

3 files changed

+94
-13
lines changed

3 files changed

+94
-13
lines changed

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

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24266,14 +24266,17 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
2426624266

2426724267
const targetIncludesParamSpec = reverseMatching ? !!srcType.details.paramSpec : !!destType.details.paramSpec;
2426824268

24269-
const destPositionalCount =
24270-
destParamDetails.argsIndex ?? destParamDetails.firstKeywordOnlyIndex ?? destParamDetails.params.length;
24271-
const srcPositionalCount =
24272-
srcParamDetails.argsIndex ?? srcParamDetails.firstKeywordOnlyIndex ?? srcParamDetails.params.length;
24269+
const destPositionalCount = destParamDetails.firstKeywordOnlyIndex ?? destParamDetails.params.length;
24270+
const srcPositionalCount = srcParamDetails.firstKeywordOnlyIndex ?? srcParamDetails.params.length;
2427324271
const positionalsToMatch = Math.min(destPositionalCount, srcPositionalCount);
2427424272

2427524273
// Match positional parameters.
2427624274
for (let paramIndex = 0; paramIndex < positionalsToMatch; paramIndex++) {
24275+
// Skip over the *args parameter since it's handled separately below.
24276+
if (paramIndex === destParamDetails.argsIndex) {
24277+
continue;
24278+
}
24279+
2427724280
const destParam = destParamDetails.params[paramIndex];
2427824281
const srcParam = srcParamDetails.params[paramIndex];
2427924282

@@ -24302,7 +24305,11 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
2430224305
}
2430324306
}
2430424307

24305-
if (!!destParam.param.hasDefault && !srcParam.param.hasDefault) {
24308+
if (
24309+
!!destParam.param.hasDefault &&
24310+
!srcParam.param.hasDefault &&
24311+
paramIndex !== srcParamDetails.argsIndex
24312+
) {
2430624313
diag?.createAddendum().addMessage(
2430724314
Localizer.DiagnosticAddendum.functionParamDefaultMissing().format({
2430824315
name: srcParamName,
@@ -24417,6 +24424,10 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
2441724424
// *args parameter type.
2441824425
const srcArgsType = srcParamDetails.params[srcParamDetails.argsIndex].type;
2441924426
for (let paramIndex = srcPositionalCount; paramIndex < destPositionalCount; paramIndex++) {
24427+
if (paramIndex === srcParamDetails.argsIndex) {
24428+
continue;
24429+
}
24430+
2442024431
const destParamType = destParamDetails.params[paramIndex].type;
2442124432
if (isVariadicTypeVar(destParamType) && !isVariadicTypeVar(srcArgsType)) {
2442224433
diag?.addMessage(Localizer.DiagnosticAddendum.typeVarTupleRequiresKnownLength());
@@ -24451,13 +24462,22 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
2445124462
}
2445224463
}
2445324464
} else if (!srcParamDetails.paramSpec) {
24454-
diag?.addMessage(
24455-
Localizer.DiagnosticAddendum.functionTooManyParams().format({
24456-
expected: srcPositionalCount,
24457-
received: destPositionalCount,
24458-
})
24459-
);
24460-
canAssign = false;
24465+
// If the dest contains a *args, remove it from the positional count
24466+
// because it's OK for zero source args to match it.
24467+
let adjDestPositionalCount = destPositionalCount;
24468+
if (destParamDetails.argsIndex !== undefined && destParamDetails.argsIndex < destPositionalCount) {
24469+
adjDestPositionalCount--;
24470+
}
24471+
24472+
if (srcPositionalCount < adjDestPositionalCount) {
24473+
diag?.addMessage(
24474+
Localizer.DiagnosticAddendum.functionTooManyParams().format({
24475+
expected: srcPositionalCount,
24476+
received: destPositionalCount,
24477+
})
24478+
);
24479+
canAssign = false;
24480+
}
2446124481
}
2446224482
}
2446324483

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# This sample tests the case where a Callable uses an unpacked TypeVarTuple
2+
# followed by another positional parameter.
3+
4+
from typing import TypeVarTuple, TypeVar, Generic, Callable
5+
6+
T = TypeVar("T")
7+
Ts = TypeVarTuple("Ts")
8+
9+
10+
class A(Generic[*Ts, T]):
11+
...
12+
13+
14+
def deco1(x: Callable[[*tuple[*Ts, int]], None]) -> tuple[*Ts]:
15+
...
16+
17+
18+
def deco2(x: Callable[[*tuple[*Ts, str]], None]) -> tuple[*Ts]:
19+
...
20+
21+
22+
def deco3(x: Callable[[*tuple[str, int]], None]) -> None:
23+
...
24+
25+
26+
def deco4(x: Callable[[*Ts, T], None]) -> A[*Ts, T]:
27+
return A()
28+
29+
30+
def func1(a: str, b: int) -> None:
31+
...
32+
33+
34+
def func2(a: str, b: str, c: int) -> None:
35+
...
36+
37+
38+
v1 = deco1(func1)
39+
reveal_type(v1, expected_text="tuple[str]")
40+
41+
v2 = deco1(func2)
42+
reveal_type(v2, expected_text="tuple[str, str]")
43+
44+
# This should generate an error.
45+
deco2(func1)
46+
47+
deco3(func1)
48+
49+
v3 = deco4(func1)
50+
reveal_type(v3, expected_text="A[str, int]")
51+
52+
v4 = deco4(func2)
53+
reveal_type(v4, expected_text="A[str, str, int]")

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,7 @@ test('VariadicTypeVar5', () => {
10511051

10521052
configOptions.defaultPythonVersion = PythonVersion.V3_11;
10531053
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['variadicTypeVar5.py'], configOptions);
1054-
TestUtils.validateResults(analysisResults, 8);
1054+
TestUtils.validateResults(analysisResults, 9);
10551055
});
10561056

10571057
test('VariadicTypeVar6', () => {
@@ -1222,6 +1222,14 @@ test('VariadicTypeVar26', () => {
12221222
TestUtils.validateResults(analysisResults, 3);
12231223
});
12241224

1225+
test('VariadicTypeVar27', () => {
1226+
const configOptions = new ConfigOptions(Uri.empty());
1227+
1228+
configOptions.defaultPythonVersion = PythonVersion.V3_11;
1229+
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['variadicTypeVar27.py'], configOptions);
1230+
TestUtils.validateResults(analysisResults, 1);
1231+
});
1232+
12251233
test('Match1', () => {
12261234
const configOptions = new ConfigOptions(Uri.empty());
12271235

0 commit comments

Comments
 (0)