diff --git a/packages/pyright-internal/src/analyzer/patternMatching.ts b/packages/pyright-internal/src/analyzer/patternMatching.ts index 5f48e772d2ef..2f4c244ebd73 100644 --- a/packages/pyright-internal/src/analyzer/patternMatching.ts +++ b/packages/pyright-internal/src/analyzer/patternMatching.ts @@ -1439,6 +1439,7 @@ function getSequencePatternInfo( if (typeArgs.length > patternEntryCount && patternStarEntryIndex === undefined) { typeArgs.splice(tupleIndeterminateIndex, 1); + removedIndeterminate = true; tupleIndeterminateIndex = -1; removedIndeterminate = true; } @@ -1479,6 +1480,13 @@ function getSequencePatternInfo( let isDefiniteNoMatch = false; let isPotentialNoMatch = tupleIndeterminateIndex >= 0 || removedIndeterminate; + // If we removed an unbounded entry to make the lengths match, + // this is a potential match (not definite) because the original + // tuple could have different lengths. + if (removedIndeterminate) { + isPotentialNoMatch = true; + } + // If the pattern includes a "star entry" and the tuple includes an // indeterminate-length entry that aligns to the star entry, we can // assume it will always match. @@ -1512,7 +1520,7 @@ function getSequencePatternInfo( entryTypes: isDefiniteNoMatch ? [] : typeArgs.map((t) => t.type), isIndeterminateLength: false, isTuple: true, - isUnboundedTuple: tupleIndeterminateIndex >= 0, + isUnboundedTuple: removedIndeterminate || tupleIndeterminateIndex >= 0, isDefiniteNoMatch, isPotentialNoMatch, }); diff --git a/packages/pyright-internal/src/tests/samples/matchSequenceVariadic.py b/packages/pyright-internal/src/tests/samples/matchSequenceVariadic.py new file mode 100644 index 000000000000..aa8788f184a0 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/matchSequenceVariadic.py @@ -0,0 +1,34 @@ +# This sample tests pattern matching with variadic tuple types. +# From the regression report for Pyright 1.1.408. + +from typing import TypeAlias, assert_type + +Func6Input: TypeAlias = tuple[int] | tuple[str, str] | tuple[int, *tuple[str, ...], int] + + +def func6(val: Func6Input): + match val: + case (x,): + # Type may be narrowed to tuple[int]. + # E: Argument of type "tuple[int]" cannot be assigned to parameter of type "tuple[int] | tuple[str, str] | tuple[int, *tuple[str, ...], int]" + assert_type(val, Func6Input) + assert_type(val, tuple[int]) + + case (x, y): + # Type may be narrowed to tuple[str, str] | tuple[int, int]. + # E: Argument of type "tuple[str, str] | tuple[int, int]" cannot be assigned to parameter of type "tuple[int] | tuple[str, str] | tuple[int, *tuple[str, ...], int]" + assert_type(val, Func6Input) + assert_type(val, tuple[str, str] | tuple[int, int]) + + case (x, y, z): + # Type may be narrowed to tuple[int, str, int]. + # This case should be reachable! + # E: Argument of type "tuple[int, str, int]" cannot be assigned to parameter of type "tuple[int] | tuple[str, str] | tuple[int, *tuple[str, ...], int]" + assert_type(val, Func6Input) + assert_type(val, tuple[int, str, int]) + + case (w, x, y, z): + # Type may be narrowed to tuple[int, str, str, int]. + # E: Argument of type "tuple[int, str, str, int]" cannot be assigned to parameter of type "tuple[int] | tuple[str, str] | tuple[int, *tuple[str, ...], int]" + assert_type(val, Func6Input) + assert_type(val, tuple[int, str, str, int]) diff --git a/packages/pyright-internal/src/tests/typeEvaluator6.test.ts b/packages/pyright-internal/src/tests/typeEvaluator6.test.ts index 577550e30bd4..f633c1a284d0 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator6.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator6.test.ts @@ -471,6 +471,15 @@ test('MatchSequence2', () => { TestUtils.validateResults(analysisResults, 0); }); +test('MatchSequenceVariadic', () => { + const configOptions = new ConfigOptions(Uri.empty()); + + configOptions.defaultPythonVersion = pythonVersion3_12; + const analysisResults = TestUtils.typeAnalyzeSampleFiles(['matchSequenceVariadic.py'], configOptions); + // After fix: should be 4 errors and 0 unreachable code + TestUtils.validateResults(analysisResults, 4, 0, undefined, undefined, 0); +}); + test('MatchClass1', () => { const configOptions = new ConfigOptions(Uri.empty());