Skip to content

Commit 9aaeef5

Browse files
authored
Allow slice syntax in e.g. Annotated[int, 1:3] and TensorType["batch":..., float] (#11345)
This is useful for Annotated, and crucial for downstream libraries like torchtyping.
1 parent 9bd6517 commit 9aaeef5

File tree

5 files changed

+80
-86
lines changed

5 files changed

+80
-86
lines changed

mypy/fastparse.py

Lines changed: 27 additions & 12 deletions
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
@@ -130,8 +131,6 @@ def ast3_parse(source: Union[str, bytes], filename: str, mode: str,
130131

131132
INVALID_TYPE_IGNORE: Final = 'Invalid "type: ignore" comment'
132133

133-
INVALID_SLICE_ERROR: Final = 'Slice usage in type annotation is invalid'
134-
135134
TYPE_IGNORE_PATTERN: Final = re.compile(r'[^#]*#\s*type:\s*ignore\s*(.*)')
136135

137136

@@ -1554,22 +1553,38 @@ def visit_Bytes(self, n: Bytes) -> Type:
15541553
contents = bytes_to_human_readable_repr(n.s)
15551554
return RawExpressionType(contents, 'builtins.bytes', self.line, column=n.col_offset)
15561555

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

15741589
empty_tuple_index = False
15751590
if isinstance(sliceval, ast3.Tuple):

test-data/unit/check-annotated.test

Lines changed: 17 additions & 0 deletions
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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,3 +897,39 @@ lst: List[int] = []
897897
if lst:
898898
pass
899899
[builtins fixtures/list.pyi]
900+
901+
[case testSliceInDict39]
902+
# flags: --python-version 3.9 --show-column-numbers
903+
from typing import Dict
904+
b: Dict[int, x:y]
905+
c: Dict[x:y]
906+
907+
[builtins fixtures/dict.pyi]
908+
[out]
909+
main:3:14: error: Invalid type comment or annotation [valid-type]
910+
main:3:14: note: did you mean to use ',' instead of ':' ?
911+
main:4:4: error: "dict" expects 2 type arguments, but 1 given [type-arg]
912+
main:4:9: error: Invalid type comment or annotation [valid-type]
913+
main:4:9: note: did you mean to use ',' instead of ':' ?
914+
915+
[case testSliceInDict38]
916+
# flags: --python-version 3.8 --show-column-numbers
917+
from typing import Dict
918+
b: Dict[int, x:y]
919+
c: Dict[x:y]
920+
921+
[builtins fixtures/dict.pyi]
922+
[out]
923+
main:3:14: error: Invalid type comment or annotation [valid-type]
924+
main:3:14: note: did you mean to use ',' instead of ':' ?
925+
main:4:4: error: "dict" expects 2 type arguments, but 1 given [type-arg]
926+
main:4:9: error: Invalid type comment or annotation [valid-type]
927+
main:4:9: note: did you mean to use ',' instead of ':' ?
928+
929+
930+
[case testSliceInCustomTensorType]
931+
# syntactically mimics torchtyping.TensorType
932+
class TensorType: ...
933+
t: TensorType["batch":..., float] # type: ignore
934+
reveal_type(t) # N: Revealed type is "__main__.TensorType"
935+
[builtins fixtures/tuple.pyi]

test-data/unit/check-fastparse.test

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -317,13 +317,6 @@ x = None # type: Any
317317
x @ 1
318318
x @= 1
319319

320-
[case testIncorrectTypeCommentIndex]
321-
322-
from typing import Dict
323-
x = None # type: Dict[x: y]
324-
[out]
325-
main:3: error: Slice usage in type annotation is invalid
326-
327320
[case testPrintStatementTrailingCommaFastParser_python2]
328321

329322
print 0,

test-data/unit/parse.test

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -949,73 +949,6 @@ main:1: error: invalid syntax
949949
[out version>=3.10]
950950
main:1: error: invalid syntax. Perhaps you forgot a comma?
951951

952-
[case testSliceInAnnotation39]
953-
# flags: --python-version 3.9
954-
a: Annotated[int, 1:2] # E: Slice usage in type annotation is invalid
955-
b: Dict[int, x:y] # E: Slice usage in type annotation is invalid
956-
c: Dict[x:y] # E: Slice usage in type annotation is invalid
957-
[out]
958-
959-
[case testSliceInAnnotation38]
960-
# flags: --python-version 3.8
961-
a: Annotated[int, 1:2] # E: Slice usage in type annotation is invalid
962-
b: Dict[int, x:y] # E: Slice usage in type annotation is invalid
963-
c: Dict[x:y] # E: Slice usage in type annotation is invalid
964-
[out]
965-
966-
[case testSliceInAnnotationTypeComment39]
967-
# flags: --python-version 3.9
968-
a = None # type: Annotated[int, 1:2] # E: Slice usage in type annotation is invalid
969-
b = None # type: Dict[int, x:y] # E: Slice usage in type annotation is invalid
970-
c = None # type: Dict[x:y] # E: Slice usage in type annotation is invalid
971-
[out]
972-
973-
[case testCorrectSlicesInAnnotations39]
974-
# flags: --python-version 3.9
975-
a: Annotated[int, slice(1, 2)]
976-
b: Dict[int, {x:y}]
977-
c: Dict[{x:y}]
978-
[out]
979-
MypyFile:1(
980-
AssignmentStmt:2(
981-
NameExpr(a)
982-
TempNode:2(
983-
Any)
984-
Annotated?[int?, None])
985-
AssignmentStmt:3(
986-
NameExpr(b)
987-
TempNode:3(
988-
Any)
989-
Dict?[int?, None])
990-
AssignmentStmt:4(
991-
NameExpr(c)
992-
TempNode:4(
993-
Any)
994-
Dict?[None]))
995-
996-
[case testCorrectSlicesInAnnotations38]
997-
# flags: --python-version 3.8
998-
a: Annotated[int, slice(1, 2)]
999-
b: Dict[int, {x:y}]
1000-
c: Dict[{x:y}]
1001-
[out]
1002-
MypyFile:1(
1003-
AssignmentStmt:2(
1004-
NameExpr(a)
1005-
TempNode:2(
1006-
Any)
1007-
Annotated?[int?, None])
1008-
AssignmentStmt:3(
1009-
NameExpr(b)
1010-
TempNode:3(
1011-
Any)
1012-
Dict?[int?, None])
1013-
AssignmentStmt:4(
1014-
NameExpr(c)
1015-
TempNode:4(
1016-
Any)
1017-
Dict?[None]))
1018-
1019952
[case testSliceInList39]
1020953
# flags: --python-version 3.9
1021954
x = [1, 2][1:2]

0 commit comments

Comments
 (0)