Skip to content

Commit e4c52ee

Browse files
authored
Allow slice syntax (#11406)
This is useful for Annotated, and crucial for downstream libraries like torchtyping. This PR cherrypicks #11345 onto the 0.920 release branch
1 parent 17755cc commit e4c52ee

File tree

5 files changed

+100
-11
lines changed

5 files changed

+100
-11
lines changed

mypy/fastparse.py

+27-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from mypy.util import unnamed_function
2+
import copy
23
import re
34
import sys
45
import warnings
@@ -1551,22 +1552,38 @@ def visit_Bytes(self, n: Bytes) -> Type:
15511552
contents = bytes_to_human_readable_repr(n.s)
15521553
return RawExpressionType(contents, 'builtins.bytes', self.line, column=n.col_offset)
15531554

1555+
def visit_Index(self, n: ast3.Index) -> Type:
1556+
# cast for mypyc's benefit on Python 3.9
1557+
return self.visit(cast(Any, n).value)
1558+
1559+
def visit_Slice(self, n: ast3.Slice) -> Type:
1560+
return self.invalid_type(
1561+
n, note="did you mean to use ',' instead of ':' ?"
1562+
)
1563+
15541564
# Subscript(expr value, slice slice, expr_context ctx) # Python 3.8 and before
15551565
# Subscript(expr value, expr slice, expr_context ctx) # Python 3.9 and later
15561566
def visit_Subscript(self, n: ast3.Subscript) -> Type:
15571567
if sys.version_info >= (3, 9): # Really 3.9a5 or later
15581568
sliceval: Any = n.slice
1559-
if (isinstance(sliceval, ast3.Slice) or
1560-
(isinstance(sliceval, ast3.Tuple) and
1561-
any(isinstance(x, ast3.Slice) for x in sliceval.elts))):
1562-
self.fail(TYPE_COMMENT_SYNTAX_ERROR, self.line, getattr(n, 'col_offset', -1))
1563-
return AnyType(TypeOfAny.from_error)
1569+
# Python 3.8 or earlier use a different AST structure for subscripts
1570+
elif isinstance(n.slice, ast3.Index):
1571+
sliceval: Any = n.slice.value
1572+
elif isinstance(n.slice, ast3.Slice):
1573+
sliceval = copy.deepcopy(n.slice) # so we don't mutate passed AST
1574+
if getattr(sliceval, "col_offset", None) is None:
1575+
# Fix column information so that we get Python 3.9+ message order
1576+
sliceval.col_offset = sliceval.lower.col_offset
15641577
else:
1565-
# Python 3.8 or earlier use a different AST structure for subscripts
1566-
if not isinstance(n.slice, Index):
1567-
self.fail(TYPE_COMMENT_SYNTAX_ERROR, self.line, getattr(n, 'col_offset', -1))
1568-
return AnyType(TypeOfAny.from_error)
1569-
sliceval = n.slice.value
1578+
assert isinstance(n.slice, ast3.ExtSlice)
1579+
dims = copy.deepcopy(n.slice.dims)
1580+
for s in dims:
1581+
if getattr(s, "col_offset", None) is None:
1582+
if isinstance(s, ast3.Index):
1583+
s.col_offset = s.value.col_offset # type: ignore
1584+
elif isinstance(s, ast3.Slice):
1585+
s.col_offset = s.lower.col_offset # type: ignore
1586+
sliceval = ast3.Tuple(dims, n.ctx)
15701587

15711588
empty_tuple_index = False
15721589
if isinstance(sliceval, ast3.Tuple):

test-data/unit/check-annotated.test

+17
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,20 @@ class Meta:
126126
x = Annotated[int, Meta()]
127127
reveal_type(x) # N: Revealed type is "def () -> builtins.int"
128128
[builtins fixtures/tuple.pyi]
129+
130+
[case testSliceAnnotated39]
131+
# flags: --python-version 3.9
132+
from typing_extensions import Annotated
133+
134+
a: Annotated[int, 1:2]
135+
reveal_type(a) # N: Revealed type is "builtins.int"
136+
137+
[builtins fixtures/tuple.pyi]
138+
[case testSliceAnnotated38]
139+
# flags: --python-version 3.8
140+
from typing_extensions import Annotated
141+
142+
a: Annotated[int, 1:2]
143+
reveal_type(a) # N: Revealed type is "builtins.int"
144+
145+
[builtins fixtures/tuple.pyi]

test-data/unit/check-errorcodes.test

+36
Original file line numberDiff line numberDiff line change
@@ -873,3 +873,39 @@ lst: List[int] = []
873873
if lst:
874874
pass
875875
[builtins fixtures/list.pyi]
876+
877+
[case testSliceInDict39]
878+
# flags: --python-version 3.9 --show-column-numbers
879+
from typing import Dict
880+
b: Dict[int, x:y]
881+
c: Dict[x:y]
882+
883+
[builtins fixtures/dict.pyi]
884+
[out]
885+
main:3:14: error: Invalid type comment or annotation [valid-type]
886+
main:3:14: note: did you mean to use ',' instead of ':' ?
887+
main:4:4: error: "dict" expects 2 type arguments, but 1 given [type-arg]
888+
main:4:9: error: Invalid type comment or annotation [valid-type]
889+
main:4:9: note: did you mean to use ',' instead of ':' ?
890+
891+
[case testSliceInDict38]
892+
# flags: --python-version 3.8 --show-column-numbers
893+
from typing import Dict
894+
b: Dict[int, x:y]
895+
c: Dict[x:y]
896+
897+
[builtins fixtures/dict.pyi]
898+
[out]
899+
main:3:14: error: Invalid type comment or annotation [valid-type]
900+
main:3:14: note: did you mean to use ',' instead of ':' ?
901+
main:4:4: error: "dict" expects 2 type arguments, but 1 given [type-arg]
902+
main:4:9: error: Invalid type comment or annotation [valid-type]
903+
main:4:9: note: did you mean to use ',' instead of ':' ?
904+
905+
906+
[case testSliceInCustomTensorType]
907+
# syntactically mimics torchtyping.TensorType
908+
class TensorType: ...
909+
t: TensorType["batch":..., float] # type: ignore
910+
reveal_type(t) # N: Revealed type is "__main__.TensorType"
911+
[builtins fixtures/tuple.pyi]

test-data/unit/check-fastparse.test

+5-1
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,12 @@ x @= 1
321321

322322
from typing import Dict
323323
x = None # type: Dict[x: y]
324+
325+
[builtins fixtures/dict.pyi]
324326
[out]
325-
main:3: error: syntax error in type comment
327+
main:3: error: "dict" expects 2 type arguments, but 1 given
328+
main:3: error: Invalid type comment or annotation
329+
main:3: note: did you mean to use ',' instead of ':' ?
326330

327331
[case testPrintStatementTrailingCommaFastParser_python2]
328332

test-data/unit/parse.test

+15
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,21 @@ main:1: error: invalid syntax
949949
[out version>=3.10]
950950
main:1: error: invalid syntax. Perhaps you forgot a comma?
951951

952+
[case testSliceInList39]
953+
# flags: --python-version 3.9
954+
x = [1, 2][1:2]
955+
[out]
956+
MypyFile:1(
957+
AssignmentStmt:2(
958+
NameExpr(x)
959+
IndexExpr:2(
960+
ListExpr:2(
961+
IntExpr(1)
962+
IntExpr(2))
963+
SliceExpr:2(
964+
IntExpr(1)
965+
IntExpr(2)))))
966+
952967
[case testDictionaryExpression]
953968
{}
954969
{1:x}

0 commit comments

Comments
 (0)