Skip to content

Fix tuple slice type evaluation for slices with start >= stop - #11615

Open
Henry Su (hsusul) wants to merge 1 commit into
microsoft:mainfrom
hsusul:fix/tuple-slice-empty-range
Open

Fix tuple slice type evaluation for slices with start >= stop#11615
Henry Su (hsusul) wants to merge 1 commit into
microsoft:mainfrom
hsusul:fix/tuple-slice-empty-range

Conversation

@hsusul

Copy link
Copy Markdown
Contributor

Summary

Fixes type evaluation of fixed-length tuple slicing when start >= stop (e.g. t[2:1], t[-1:-2], t[5:1]).

In Python runtime, slicing a tuple with start >= stop produces an empty tuple () of length 0.

Previously, getSlicedTupleType returned undefined whenever endValue < startValue. When getSlicedTupleType returned undefined, Pyright fell back to evaluating tuple.__getitem__(slice), which degraded the inferred type of a fixed-length heterogeneous tuple slice (such as t[2:1] where t: tuple[int, str, bool]) to an unbounded union type tuple[int | str | bool, ...].

Behavior Change

Pyright now correctly evaluates fixed-length tuple slicing with start >= stop to an empty tuple type tuple[()].

Implementation Details

In packages/pyright-internal/src/analyzer/tuples.ts, getSlicedTupleType no longer returns undefined when endValue < startValue. Instead, it checks startValue < endValue and uses an empty array [] when startValue >= endValue, which cleanly specializes to tuple[()].

Validation

  • Added new sample test tuple20.py and test case Tuple20 in typeEvaluator8.test.ts.
  • Ran full test suite in packages/pyright-internal (npm run test): 24 test suites passed (798 passed, 0 failed).
  • Ran workspace check script (npm run check): 4 packages typechecked cleanly with 0 errors.
  • Ran git diff --check: 0 issues found.

@StellaHuang95

Copy link
Copy Markdown
Collaborator

Please mirror this fix in packages/pylance-internal/src/analyzer/tuples.ts; its async implementation still returns the unbounded fallback for fixed-length slices where start >= stop.

@StellaHuang95 Stella Huang (StellaHuang95) added the review-auto:changes-requested Automated review: posted blocking findings to address. label Aug 10, 2026
@hsusul

Copy link
Copy Markdown
Contributor Author

Thanks Stella Huang (@StellaHuang95)! is in the internal Pylance repository and isn't present in the open-source tree, but this change in is complete and will mirror cleanly into Pylance when synced.

@StellaHuang95 Stella Huang (StellaHuang95) removed the review-auto:changes-requested Automated review: posted blocking findings to address. label Aug 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

sympy (https://github.com/sympy/sympy)
-   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:176:44 - error: Cannot access attribute "expand" for class "Basic"
-     Attribute "expand" is unknown (reportAttributeAccessIssue)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:314:30 - error: Cannot access attribute "as_independent" for class "Basic"
-     Attribute "as_independent" is unknown (reportAttributeAccessIssue)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:383:30 - error: Cannot access attribute "as_independent" for class "Basic"
-     Attribute "as_independent" is unknown (reportAttributeAccessIssue)
-   .../projects/sympy/sympy/solvers/ode/hypergeometric.py:247:67 - error: Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
-   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:225:45 - error: Cannot access attribute "has" for class "tuple[Expr, int]"
-     Attribute "has" is unknown (reportAttributeAccessIssue)
+   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:223:22 - error: No overloads for "__new__" match the provided arguments (reportCallIssue)
+   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:223:25 - error: Argument of type "CRootOf | tuple[Expr, int]" cannot be assigned to parameter "arg" of type "Expr" in function "__new__"
+     Type "CRootOf | tuple[Expr, int]" is not assignable to type "Expr"
+       "tuple[Expr, int]" is not assignable to "Expr" (reportArgumentType)
+   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:238:40 - error: No overloads for "__new__" match the provided arguments (reportCallIssue)
+   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:238:50 - error: Argument of type "CRootOf | tuple[Expr, int]" cannot be assigned to parameter "arg" of type "Expr" in function "__new__"
+     Type "CRootOf | tuple[Expr, int]" is not assignable to type "Expr"
+       "tuple[Expr, int]" is not assignable to "Expr" (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:468:28 - error: Argument of type "Expr | Unknown | None" cannot be assigned to parameter "expr" of type "Expr" in function "make_args"
+   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:468:28 - error: Argument of type "Unknown | None" cannot be assigned to parameter "expr" of type "Expr" in function "make_args"
-     Type "Expr | Unknown | None" is not assignable to type "Expr"
+     Type "Unknown | None" is not assignable to type "Expr"
-   .../projects/sympy/sympy/solvers/ode/ode.py:1579:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1580:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1590:9 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1590:12 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1590:19 - error: Argument of type "Unknown | dict[Unknown, Unknown] | None" cannot be assigned to parameter "m" of type "Iterable[tuple[str, Unknown]]" in function "update"
-     Type "Unknown | dict[Unknown, Unknown] | None" is not assignable to type "Iterable[tuple[str, Unknown]]"
-       "None" is incompatible with protocol "Iterable[tuple[str, Unknown]]"
-         "__iter__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1597:43 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1597:64 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1603:9 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1603:12 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1603:19 - error: Argument of type "Unknown | dict[Unknown, Unknown] | None" cannot be assigned to parameter "m" of type "Iterable[tuple[str, Unknown]]" in function "update"
-     Type "Unknown | dict[Unknown, Unknown] | None" is not assignable to type "Iterable[tuple[str, Unknown]]"
-       "None" is incompatible with protocol "Iterable[tuple[str, Unknown]]"
-         "__iter__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1610:43 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1610:74 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1616:9 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1616:12 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1616:19 - error: Argument of type "Unknown | dict[Unknown, Unknown] | None" cannot be assigned to parameter "m" of type "Iterable[tuple[str, Unknown]]" in function "update"
-     Type "Unknown | dict[Unknown, Unknown] | None" is not assignable to type "Iterable[tuple[str, Unknown]]"
-       "None" is incompatible with protocol "Iterable[tuple[str, Unknown]]"
-         "__iter__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1623:49 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1623:70 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1753:36 - error: Cannot access attribute "lhs" for class "Expr"
+     Attribute "lhs" is unknown (reportAttributeAccessIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1753:55 - error: Cannot access attribute "rhs" for class "Expr"
+     Attribute "rhs" is unknown (reportAttributeAccessIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1754:36 - error: Cannot access attribute "lhs" for class "Expr"
+     Attribute "lhs" is unknown (reportAttributeAccessIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1755:17 - error: No overloads for "__setitem__" match the provided arguments (reportCallIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1755:17 - error: Argument of type "Equality | BooleanFalse | BooleanTrue | Unknown | Expr" cannot be assigned to parameter "value" of type "Equality | BooleanFalse | BooleanTrue" in function "__setitem__"
+     Type "Equality | BooleanFalse | BooleanTrue | Unknown | Expr" is not assignable to type "Equality | BooleanFalse | BooleanTrue"
+       Type "Expr" is not assignable to type "Equality | BooleanFalse | BooleanTrue"
+         "Expr" is not assignable to "Equality"
+         "Expr" is not assignable to "BooleanFalse"
+         "Expr" is not assignable to "BooleanTrue" (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3442:7 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3442:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3443:7 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3443:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3444:14 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3445:14 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3446:14 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3458:50 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3459:50 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3460:50 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3500:5 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3500:7 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3500:14 - error: Argument of type "Unknown | dict[Unknown, Unknown] | None" cannot be assigned to parameter "m" of type "Iterable[tuple[str, Unknown]]" in function "update"
-     Type "Unknown | dict[Unknown, Unknown] | None" is not assignable to type "Iterable[tuple[str, Unknown]]"
-       "None" is incompatible with protocol "Iterable[tuple[str, Unknown]]"
-         "__iter__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3501:18 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3501:43 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3502:9 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3502:16 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3502:24 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)

... (truncated 1342 lines) ...

@rchiodo

Rich Chiodo (rchiodo) commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

🔒 Automated review in progress — Rich Chiodo (@rchiodo) is auto-reviewing this PR.

if (startValue === undefined || endValue === undefined || endValue < startValue) {
if (startValue === undefined || endValue === undefined) {
return undefined;
}

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.

Warning · Non-blocking recommendation

t[True:False] and t[2:1:None] still infer an unbounded tuple instead of tuple[()]. Normalize literal booleans as integer bounds, treat an explicit None step like an omitted step, and add regression assertions for both cases.

[verified]

reveal_type(b, expected_text="tuple[()]")

c = t[5:1]
reveal_type(c, expected_text="tuple[()]")

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.

Warning · Non-blocking recommendation

tuple13.py already owns tuple slice-expression coverage, including empty and reversed slices. Move these cases there and remove the additional sample registration so the behavior remains specified in one place.

[verified]

@rchiodo

Copy link
Copy Markdown
Collaborator

When this change is vendored into Pylance, update its parallel async tuple evaluator as well; it still has the old reversed-bound guard and would otherwise diverge.

@rchiodo Rich Chiodo (rchiodo) left a comment

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.

Approved via Review Center.

@rchiodo Rich Chiodo (rchiodo) added the review-auto:approved Automated review: no blocking findings (approval posted). label Aug 13, 2026

@rchiodo Rich Chiodo (rchiodo) left a comment

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.

Approved via Review Center.

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.

Approved via Review Center.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-auto:approved Automated review: no blocking findings (approval posted).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants