Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/pyright-internal/src/analyzer/patternMatching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,7 @@ function getSequencePatternInfo(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot generated:
-1442

After tupleIndeterminateIndex = -1, the resulting SequencePatternInfo will have isUnboundedTuple: false even though the original tuple IS unbounded. This works today because isPotentialNoMatch = true prevents elimination before isUnboundedTuple is consulted, but any future consumer of isUnboundedTuple that trusts it as ground truth will get incorrect information. Consider: isUnboundedTuple: removedIndeterminate || tupleIndeterminateIndex >= 0 at the push site (around line 1510) to preserve semantic accuracy.

if (typeArgs.length > patternEntryCount && patternStarEntryIndex === undefined) {
typeArgs.splice(tupleIndeterminateIndex, 1);
removedIndeterminate = true;
tupleIndeterminateIndex = -1;
removedIndeterminate = true;
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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])
9 changes: 9 additions & 0 deletions packages/pyright-internal/src/tests/typeEvaluator6.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down
Loading