Skip to content

Commit

Permalink
Fixed bug that results in incorrect evaluation when passing a callabl…
Browse files Browse the repository at this point in the history
…e with a "*args" parameter to a callable with a `Concatenate` and `ParamSpec`. This addresses #9630. (#9632)
  • Loading branch information
erictraut authored Dec 25, 2024
1 parent e643c25 commit db368a1
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 12 deletions.
35 changes: 23 additions & 12 deletions packages/pyright-internal/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26490,21 +26490,32 @@ export function createTypeEvaluator(
if (p.name) {
matchedParamCount++;
}
} else if (isPositionOnlySeparator(p) && remainingParams.length === 0) {

// If this is a *args parameter, assume that it provides
// the remaining positional parameters, but also assume
// that it is not exhausted and can provide additional
// parameters.
if (p.category !== ParamCategory.ArgsList) {
return;
}
}

if (isPositionOnlySeparator(p) && remainingParams.length === 0) {
// Don't bother pushing a position-only separator if it
// is the first remaining param.
} else {
remainingParams.push(
FunctionParam.create(
p.category,
FunctionType.getParamType(effectiveSrcType, index),
p.flags,
p.name,
FunctionType.getParamDefaultType(effectiveSrcType, index),
p.defaultExpr
)
);
return;
}

remainingParams.push(
FunctionParam.create(
p.category,
FunctionType.getParamType(effectiveSrcType, index),
p.flags,
p.name,
FunctionType.getParamDefaultType(effectiveSrcType, index),
p.defaultExpr
)
);
});

// If there are remaining parameters and the source and dest do not contain
Expand Down
35 changes: 35 additions & 0 deletions packages/pyright-internal/src/tests/samples/paramSpec54.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# This sample tests a function that uses a Concatenate with a callback
# that has a *args parameter.

from typing import Callable, Concatenate, reveal_type


def func1[T, **P, R](fn: Callable[Concatenate[T, P], R], val: T) -> Callable[P, R]:
...


def test1(*args: str) -> None:
...


reveal_type(func1(test1, ""), expected_text="(*args: str) -> None")
reveal_type(func1(func1(test1, ""), ""), expected_text="(*args: str) -> None")


def test2(p1: int, *args: str) -> None:
...


reveal_type(func1(test2, 0), expected_text="(*args: str) -> None")
reveal_type(func1(func1(test2, 0), ""), expected_text="(*args: str) -> None")
reveal_type(func1(func1(func1(test2, 0), ""), ""), expected_text="(*args: str) -> None")


def func2[T1, T2, **P, R](
fn: Callable[Concatenate[T1, T2, P], R], val1: T1, val2: T2
) -> Callable[P, R]:
...


reveal_type(func2(test1, "", ""), expected_text="(*args: str) -> None")
reveal_type(func2(func2(test1, "", ""), "", ""), expected_text="(*args: str) -> None")
5 changes: 5 additions & 0 deletions packages/pyright-internal/src/tests/typeEvaluator4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,11 @@ test('ParamSpec53', () => {
TestUtils.validateResults(results, 0);
});

test('ParamSpec54', () => {
const results = TestUtils.typeAnalyzeSampleFiles(['paramSpec54.py']);
TestUtils.validateResults(results, 0);
});

test('Slice1', () => {
const results = TestUtils.typeAnalyzeSampleFiles(['slice1.py']);
TestUtils.validateResults(results, 0);
Expand Down

0 comments on commit db368a1

Please sign in to comment.