Skip to content

Commit 486bb69

Browse files
axitkhuranagnprice
authored andcommitted
Support negative indexes in tuple slices (#1620)
Also fix the defaults for start, end, and stride to `None` to correctly match the Python slice semantics -- in particular, if the stride is negative then the default start and end are effectively `length - 1` and `0` respectively rather than vice versa. Fixes #886, except for the part that's been separated out as #899.
1 parent 20d0c72 commit 486bb69

File tree

2 files changed

+33
-15
lines changed

2 files changed

+33
-15
lines changed

mypy/checkexpr.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,31 +1136,47 @@ def visit_index_expr_helper(self, e: IndexExpr) -> Type:
11361136
return result
11371137

11381138
def visit_tuple_slice_helper(self, left_type: TupleType, slic: SliceExpr):
1139-
begin = 0
1140-
end = len(left_type.items)
1141-
stride = 1
1139+
begin = None # type: int
1140+
end = None # type: int
1141+
stride = None # type:int
1142+
11421143
if slic.begin_index:
1143-
if isinstance(slic.begin_index, IntExpr):
1144-
begin = slic.begin_index.value
1145-
else:
1146-
self.chk.fail(messages.TUPLE_SLICE_MUST_BE_AN_INT_LITERAL, slic.begin_index)
1144+
begin = self._get_value(slic.begin_index)
1145+
if begin is None:
1146+
self.chk.fail(
1147+
messages.TUPLE_SLICE_MUST_BE_AN_INT_LITERAL,
1148+
slic.begin_index)
11471149
return AnyType()
1150+
11481151
if slic.end_index:
1149-
if isinstance(slic.end_index, IntExpr):
1150-
end = slic.end_index.value
1151-
else:
1152-
self.chk.fail(messages.TUPLE_SLICE_MUST_BE_AN_INT_LITERAL, slic.end_index)
1152+
end = self._get_value(slic.end_index)
1153+
if end is None:
1154+
self.chk.fail(
1155+
messages.TUPLE_SLICE_MUST_BE_AN_INT_LITERAL,
1156+
slic.end_index)
11531157
return AnyType()
1158+
11541159
if slic.stride:
1155-
if isinstance(slic.stride, IntExpr):
1156-
stride = slic.stride.value
1157-
else:
1158-
self.chk.fail(messages.TUPLE_SLICE_MUST_BE_AN_INT_LITERAL, slic.stride)
1160+
stride = self._get_value(slic.stride)
1161+
if stride is None:
1162+
self.chk.fail(
1163+
messages.TUPLE_SLICE_MUST_BE_AN_INT_LITERAL,
1164+
slic.stride)
11591165
return AnyType()
11601166

11611167
return TupleType(left_type.items[begin:end:stride], left_type.fallback,
11621168
left_type.line, left_type.implicit)
11631169

1170+
def _get_value(self, index: Node) -> Optional[int]:
1171+
if isinstance(index, IntExpr):
1172+
return index.value
1173+
elif isinstance(index, UnaryExpr):
1174+
if index.op == '-':
1175+
operand = index.expr
1176+
if isinstance(operand, IntExpr):
1177+
return -1 * operand.value
1178+
return None
1179+
11641180
def visit_cast_expr(self, expr: CastExpr) -> Type:
11651181
"""Type check a cast expression."""
11661182
source_type = self.accept(expr.expr, context=AnyType())

mypy/test/data/check-tuples.test

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,11 @@ b = t1[(0)] # E: Incompatible types in assignment (expression has type "A", vari
169169

170170
a = t1[0]
171171
b = t1[1]
172+
b = t1[-1]
172173
a = t1[(0)]
173174
x = t3[0:3] # type (A, B, C)
174175
y = t3[0:5:2] # type (A, C, E)
176+
x = t3[:-2] # type (A, B, C)
175177

176178
class A: pass
177179
class B: pass

0 commit comments

Comments
 (0)