8
8
import ast
9
9
import bisect
10
10
import collections
11
+ import contextlib
11
12
import doctest
12
13
import functools
13
14
import os
@@ -663,17 +664,26 @@ def getNodeName(node):
663
664
return node .name
664
665
665
666
666
- def _is_typing (node , typing_attr , scope_stack ):
667
+ TYPING_MODULES = frozenset (('typing' , 'typing_extensions' ))
668
+
669
+
670
+ def _is_typing_helper (node , is_name_match_fn , scope_stack ):
671
+ """
672
+ Internal helper to determine whether or not something is a member of a
673
+ typing module. This is used as part of working out whether we are within a
674
+ type annotation context.
675
+
676
+ Note: you probably don't want to use this function directly. Instead see the
677
+ utils below which wrap it (`_is_typing` and `_is_any_typing_member`).
678
+ """
679
+
667
680
def _bare_name_is_attr (name ):
668
- expected_typing_names = {
669
- 'typing.{}' .format (typing_attr ),
670
- 'typing_extensions.{}' .format (typing_attr ),
671
- }
672
681
for scope in reversed (scope_stack ):
673
682
if name in scope :
674
683
return (
675
684
isinstance (scope [name ], ImportationFrom ) and
676
- scope [name ].fullName in expected_typing_names
685
+ scope [name ].module in TYPING_MODULES and
686
+ is_name_match_fn (scope [name ].real_name )
677
687
)
678
688
679
689
return False
@@ -685,12 +695,33 @@ def _bare_name_is_attr(name):
685
695
) or (
686
696
isinstance (node , ast .Attribute ) and
687
697
isinstance (node .value , ast .Name ) and
688
- node .value .id in { 'typing' , 'typing_extensions' } and
689
- node .attr == typing_attr
698
+ node .value .id in TYPING_MODULES and
699
+ is_name_match_fn ( node .attr )
690
700
)
691
701
)
692
702
693
703
704
+ def _is_typing (node , typing_attr , scope_stack ):
705
+ """
706
+ Determine whether `node` represents the member of a typing module specified
707
+ by `typing_attr`.
708
+
709
+ This is used as part of working out whether we are within a type annotation
710
+ context.
711
+ """
712
+ return _is_typing_helper (node , lambda x : x == typing_attr , scope_stack )
713
+
714
+
715
+ def _is_any_typing_member (node , scope_stack ):
716
+ """
717
+ Determine whether `node` represents any member of a typing module.
718
+
719
+ This is used as part of working out whether we are within a type annotation
720
+ context.
721
+ """
722
+ return _is_typing_helper (node , lambda x : True , scope_stack )
723
+
724
+
694
725
def is_typing_overload (value , scope_stack ):
695
726
return (
696
727
isinstance (value .source , FUNCTION_TYPES ) and
@@ -704,11 +735,8 @@ def is_typing_overload(value, scope_stack):
704
735
def in_annotation (func ):
705
736
@functools .wraps (func )
706
737
def in_annotation_func (self , * args , ** kwargs ):
707
- orig , self ._in_annotation = self ._in_annotation , True
708
- try :
738
+ with self ._enter_annotation ():
709
739
return func (self , * args , ** kwargs )
710
- finally :
711
- self ._in_annotation = orig
712
740
return in_annotation_func
713
741
714
742
@@ -1236,6 +1264,14 @@ def on_conditional_branch():
1236
1264
except KeyError :
1237
1265
self .report (messages .UndefinedName , node , name )
1238
1266
1267
+ @contextlib .contextmanager
1268
+ def _enter_annotation (self ):
1269
+ orig , self ._in_annotation = self ._in_annotation , True
1270
+ try :
1271
+ yield
1272
+ finally :
1273
+ self ._in_annotation = orig
1274
+
1239
1275
def _handle_type_comments (self , node ):
1240
1276
for (lineno , col_offset ), comment in self ._type_comments .get (node , ()):
1241
1277
comment = comment .split (':' , 1 )[1 ].strip ()
@@ -1428,7 +1464,11 @@ def SUBSCRIPT(self, node):
1428
1464
finally :
1429
1465
self ._in_typing_literal = orig
1430
1466
else :
1431
- self .handleChildren (node )
1467
+ if _is_any_typing_member (node .value , self .scopeStack ):
1468
+ with self ._enter_annotation ():
1469
+ self .handleChildren (node )
1470
+ else :
1471
+ self .handleChildren (node )
1432
1472
1433
1473
def _handle_string_dot_format (self , node ):
1434
1474
try :
@@ -1557,6 +1597,15 @@ def CALL(self, node):
1557
1597
node .func .attr == 'format'
1558
1598
):
1559
1599
self ._handle_string_dot_format (node )
1600
+
1601
+ if (
1602
+ _is_typing (node .func , 'cast' , self .scopeStack ) and
1603
+ len (node .args ) >= 1 and
1604
+ isinstance (node .args [0 ], ast .Str )
1605
+ ):
1606
+ with self ._enter_annotation ():
1607
+ self .handleNode (node .args [0 ], node )
1608
+
1560
1609
self .handleChildren (node )
1561
1610
1562
1611
def _handle_percent_format (self , node ):
0 commit comments