Commit b32cc41f authored by Guido van Rossum's avatar Guido van Rossum

Issue #28556: More typing.py updates from upstream.

parent 47c1c874
...@@ -378,6 +378,16 @@ class CallableTests(BaseTestCase): ...@@ -378,6 +378,16 @@ class CallableTests(BaseTestCase):
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
type(c)() type(c)()
def test_callable_wrong_forms(self):
with self.assertRaises(TypeError):
Callable[[...], int]
with self.assertRaises(TypeError):
Callable[(), int]
with self.assertRaises(TypeError):
Callable[[()], int]
with self.assertRaises(TypeError):
Callable[[int, 1], 2]
def test_callable_instance_works(self): def test_callable_instance_works(self):
def f(): def f():
pass pass
...@@ -1296,9 +1306,10 @@ PY36 = sys.version_info[:2] >= (3, 6) ...@@ -1296,9 +1306,10 @@ PY36 = sys.version_info[:2] >= (3, 6)
PY36_TESTS = """ PY36_TESTS = """
from test import ann_module, ann_module2, ann_module3 from test import ann_module, ann_module2, ann_module3
from collections import ChainMap
class B: class A:
y: float
class B(A):
x: ClassVar[Optional['B']] = None x: ClassVar[Optional['B']] = None
y: int y: int
class CSub(B): class CSub(B):
...@@ -1317,6 +1328,15 @@ if PY36: ...@@ -1317,6 +1328,15 @@ if PY36:
gth = get_type_hints gth = get_type_hints
class GetTypeHintTests(BaseTestCase): class GetTypeHintTests(BaseTestCase):
def test_get_type_hints_from_various_objects(self):
# For invalid objects should fail with TypeError (not AttributeError etc).
with self.assertRaises(TypeError):
gth(123)
with self.assertRaises(TypeError):
gth('abc')
with self.assertRaises(TypeError):
gth(None)
@skipUnless(PY36, 'Python 3.6 required') @skipUnless(PY36, 'Python 3.6 required')
def test_get_type_hints_modules(self): def test_get_type_hints_modules(self):
self.assertEqual(gth(ann_module), {1: 2, 'f': Tuple[int, int], 'x': int, 'y': str}) self.assertEqual(gth(ann_module), {1: 2, 'f': Tuple[int, int], 'x': int, 'y': str})
...@@ -1326,18 +1346,15 @@ class GetTypeHintTests(BaseTestCase): ...@@ -1326,18 +1346,15 @@ class GetTypeHintTests(BaseTestCase):
@skipUnless(PY36, 'Python 3.6 required') @skipUnless(PY36, 'Python 3.6 required')
def test_get_type_hints_classes(self): def test_get_type_hints_classes(self):
self.assertEqual(gth(ann_module.C, ann_module.__dict__), self.assertEqual(gth(ann_module.C, ann_module.__dict__),
ChainMap({'y': Optional[ann_module.C]}, {})) {'y': Optional[ann_module.C]})
self.assertEqual(repr(gth(ann_module.j_class)), 'ChainMap({}, {})') self.assertIsInstance(gth(ann_module.j_class), dict)
self.assertEqual(gth(ann_module.M), ChainMap({'123': 123, 'o': type}, self.assertEqual(gth(ann_module.M), {'123': 123, 'o': type})
{}, {}))
self.assertEqual(gth(ann_module.D), self.assertEqual(gth(ann_module.D),
ChainMap({'j': str, 'k': str, {'j': str, 'k': str, 'y': Optional[ann_module.C]})
'y': Optional[ann_module.C]}, {})) self.assertEqual(gth(ann_module.Y), {'z': int})
self.assertEqual(gth(ann_module.Y), ChainMap({'z': int}, {}))
self.assertEqual(gth(ann_module.h_class), self.assertEqual(gth(ann_module.h_class),
ChainMap({}, {'y': Optional[ann_module.C]}, {})) {'y': Optional[ann_module.C]})
self.assertEqual(gth(ann_module.S), ChainMap({'x': str, 'y': str}, self.assertEqual(gth(ann_module.S), {'x': str, 'y': str})
{}))
self.assertEqual(gth(ann_module.foo), {'x': int}) self.assertEqual(gth(ann_module.foo), {'x': int})
@skipUnless(PY36, 'Python 3.6 required') @skipUnless(PY36, 'Python 3.6 required')
...@@ -1355,20 +1372,34 @@ class GetTypeHintTests(BaseTestCase): ...@@ -1355,20 +1372,34 @@ class GetTypeHintTests(BaseTestCase):
class Der(ABase): ... class Der(ABase): ...
self.assertEqual(gth(ABase.meth), {'x': int}) self.assertEqual(gth(ABase.meth), {'x': int})
def test_get_type_hints_for_builins(self):
# Should not fail for built-in classes and functions.
self.assertEqual(gth(int), {})
self.assertEqual(gth(type), {})
self.assertEqual(gth(dir), {})
self.assertEqual(gth(len), {})
def test_previous_behavior(self): def test_previous_behavior(self):
def testf(x, y): ... def testf(x, y): ...
testf.__annotations__['x'] = 'int' testf.__annotations__['x'] = 'int'
self.assertEqual(gth(testf), {'x': int}) self.assertEqual(gth(testf), {'x': int})
def test_get_type_hints_for_object_with_annotations(self):
class A: ...
class B: ...
b = B()
b.__annotations__ = {'x': 'A'}
self.assertEqual(gth(b, locals()), {'x': A})
@skipUnless(PY36, 'Python 3.6 required') @skipUnless(PY36, 'Python 3.6 required')
def test_get_type_hints_ClassVar(self): def test_get_type_hints_ClassVar(self):
self.assertEqual(gth(ann_module2.CV, ann_module2.__dict__),
{'var': typing.ClassVar[ann_module2.CV]})
self.assertEqual(gth(B, globals()), self.assertEqual(gth(B, globals()),
ChainMap({'y': int, 'x': ClassVar[Optional[B]]}, {})) {'y': int, 'x': ClassVar[Optional[B]]})
self.assertEqual(gth(CSub, globals()), self.assertEqual(gth(CSub, globals()),
ChainMap({'z': ClassVar[CSub]}, {'z': ClassVar[CSub], 'y': int, 'x': ClassVar[Optional[B]]})
{'y': int, 'x': ClassVar[Optional[B]]}, {})) self.assertEqual(gth(G), {'lst': ClassVar[List[T]]})
self.assertEqual(gth(G), ChainMap({'lst': ClassVar[List[T]]},{},{}))
class CollectionsAbcTests(BaseTestCase): class CollectionsAbcTests(BaseTestCase):
......
...@@ -10,8 +10,6 @@ try: ...@@ -10,8 +10,6 @@ try:
import collections.abc as collections_abc import collections.abc as collections_abc
except ImportError: except ImportError:
import collections as collections_abc # Fallback for PY3.2. import collections as collections_abc # Fallback for PY3.2.
if sys.version_info[:2] >= (3, 3):
from collections import ChainMap
# Please keep __all__ alphabetized within each category. # Please keep __all__ alphabetized within each category.
...@@ -1194,14 +1192,12 @@ class CallableMeta(GenericMeta): ...@@ -1194,14 +1192,12 @@ class CallableMeta(GenericMeta):
# super()._tree_repr() for nice formatting. # super()._tree_repr() for nice formatting.
arg_list = [] arg_list = []
for arg in tree[1:]: for arg in tree[1:]:
if arg == (): if not isinstance(arg, tuple):
arg_list.append('[]')
elif not isinstance(arg, tuple):
arg_list.append(_type_repr(arg)) arg_list.append(_type_repr(arg))
else: else:
arg_list.append(arg[0]._tree_repr(arg)) arg_list.append(arg[0]._tree_repr(arg))
if len(arg_list) == 2: if arg_list[0] == '...':
return repr(tree[0]) + '[%s]' % ', '.join(arg_list) return repr(tree[0]) + '[..., %s]' % arg_list[1]
return (repr(tree[0]) + return (repr(tree[0]) +
'[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1])) '[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1]))
...@@ -1216,26 +1212,22 @@ class CallableMeta(GenericMeta): ...@@ -1216,26 +1212,22 @@ class CallableMeta(GenericMeta):
raise TypeError("Callable must be used as " raise TypeError("Callable must be used as "
"Callable[[arg, ...], result].") "Callable[[arg, ...], result].")
args, result = parameters args, result = parameters
if args is ...: if args is Ellipsis:
parameters = (..., result) parameters = (Ellipsis, result)
elif args == []:
parameters = ((), result)
else: else:
if not isinstance(args, list): if not isinstance(args, list):
raise TypeError("Callable[args, result]: args must be a list." raise TypeError("Callable[args, result]: args must be a list."
" Got %.100r." % (args,)) " Got %.100r." % (args,))
parameters = tuple(args) + (result,) parameters = (tuple(args), result)
return self.__getitem_inner__(parameters) return self.__getitem_inner__(parameters)
@_tp_cache @_tp_cache
def __getitem_inner__(self, parameters): def __getitem_inner__(self, parameters):
*args, result = parameters args, result = parameters
msg = "Callable[args, result]: result must be a type." msg = "Callable[args, result]: result must be a type."
result = _type_check(result, msg) result = _type_check(result, msg)
if args == [...,]: if args is Ellipsis:
return super().__getitem__((_TypingEllipsis, result)) return super().__getitem__((_TypingEllipsis, result))
if args == [(),]:
return super().__getitem__((_TypingEmpty, result))
msg = "Callable[[arg, ...], result]: each arg must be a type." msg = "Callable[[arg, ...], result]: each arg must be a type."
args = tuple(_type_check(arg, msg) for arg in args) args = tuple(_type_check(arg, msg) for arg in args)
parameters = args + (result,) parameters = args + (result,)
...@@ -1332,7 +1324,11 @@ def cast(typ, val): ...@@ -1332,7 +1324,11 @@ def cast(typ, val):
def _get_defaults(func): def _get_defaults(func):
"""Internal helper to extract the default arguments, by name.""" """Internal helper to extract the default arguments, by name."""
code = func.__code__ try:
code = func.__code__
except AttributeError:
# Some built-in functions don't have __code__, __defaults__, etc.
return {}
pos_count = code.co_argcount pos_count = code.co_argcount
arg_names = code.co_varnames arg_names = code.co_varnames
arg_names = arg_names[:pos_count] arg_names = arg_names[:pos_count]
...@@ -1346,138 +1342,80 @@ def _get_defaults(func): ...@@ -1346,138 +1342,80 @@ def _get_defaults(func):
return res return res
if sys.version_info[:2] >= (3, 3): def get_type_hints(obj, globalns=None, localns=None):
def get_type_hints(obj, globalns=None, localns=None): """Return type hints for an object.
"""Return type hints for an object.
This is often the same as obj.__annotations__, but it handles This is often the same as obj.__annotations__, but it handles
forward references encoded as string literals, and if necessary forward references encoded as string literals, and if necessary
adds Optional[t] if a default value equal to None is set. adds Optional[t] if a default value equal to None is set.
The argument may be a module, class, method, or function. The annotations The argument may be a module, class, method, or function. The annotations
are returned as a dictionary, or in the case of a class, a ChainMap of are returned as a dictionary. For classes, annotations include also
dictionaries. inherited members.
TypeError is raised if the argument is not of a type that can contain TypeError is raised if the argument is not of a type that can contain
annotations, and an empty dictionary is returned if no annotations are annotations, and an empty dictionary is returned if no annotations are
present. present.
BEWARE -- the behavior of globalns and localns is counterintuitive BEWARE -- the behavior of globalns and localns is counterintuitive
(unless you are familiar with how eval() and exec() work). The (unless you are familiar with how eval() and exec() work). The
search order is locals first, then globals. search order is locals first, then globals.
- If no dict arguments are passed, an attempt is made to use the - If no dict arguments are passed, an attempt is made to use the
globals from obj, and these are also used as the locals. If the globals from obj, and these are also used as the locals. If the
object does not appear to have globals, an exception is raised. object does not appear to have globals, an exception is raised.
- If one dict argument is passed, it is used for both globals and - If one dict argument is passed, it is used for both globals and
locals. locals.
- If two dict arguments are passed, they specify globals and - If two dict arguments are passed, they specify globals and
locals, respectively. locals, respectively.
""" """
if getattr(obj, '__no_type_check__', None): if getattr(obj, '__no_type_check__', None):
return {} return {}
if globalns is None: if globalns is None:
globalns = getattr(obj, '__globals__', {}) globalns = getattr(obj, '__globals__', {})
if localns is None: if localns is None:
localns = globalns
elif localns is None:
localns = globalns localns = globalns
elif localns is None:
if (isinstance(obj, types.FunctionType) or localns = globalns
isinstance(obj, types.BuiltinFunctionType) or # Classes require a special treatment.
isinstance(obj, types.MethodType)): if isinstance(obj, type):
defaults = _get_defaults(obj) hints = {}
hints = obj.__annotations__ for base in reversed(obj.__mro__):
for name, value in hints.items(): ann = base.__dict__.get('__annotations__', {})
if value is None: for name, value in ann.items():
value = type(None)
if isinstance(value, str):
value = _ForwardRef(value)
value = _eval_type(value, globalns, localns)
if name in defaults and defaults[name] is None:
value = Optional[value]
hints[name] = value
return hints
if isinstance(obj, types.ModuleType):
try:
hints = obj.__annotations__
except AttributeError:
return {}
for name, value in hints.items():
if value is None: if value is None:
value = type(None) value = type(None)
if isinstance(value, str): if isinstance(value, str):
value = _ForwardRef(value) value = _ForwardRef(value)
value = _eval_type(value, globalns, localns) value = _eval_type(value, globalns, localns)
hints[name] = value hints[name] = value
return hints
if isinstance(object, type):
cmap = None
for base in reversed(obj.__mro__):
new_map = collections.ChainMap if cmap is None else cmap.new_child
try:
hints = base.__dict__['__annotations__']
except KeyError:
cmap = new_map()
else:
for name, value in hints.items():
if value is None:
value = type(None)
if isinstance(value, str):
value = _ForwardRef(value)
value = _eval_type(value, globalns, localns)
hints[name] = value
cmap = new_map(hints)
return cmap
raise TypeError('{!r} is not a module, class, method, '
'or function.'.format(obj))
else:
def get_type_hints(obj, globalns=None, localns=None):
"""Return type hints for a function or method object.
This is often the same as obj.__annotations__, but it handles
forward references encoded as string literals, and if necessary
adds Optional[t] if a default value equal to None is set.
BEWARE -- the behavior of globalns and localns is counterintuitive
(unless you are familiar with how eval() and exec() work). The
search order is locals first, then globals.
- If no dict arguments are passed, an attempt is made to use the
globals from obj, and these are also used as the locals. If the
object does not appear to have globals, an exception is raised.
- If one dict argument is passed, it is used for both globals and
locals.
- If two dict arguments are passed, they specify globals and
locals, respectively.
"""
if getattr(obj, '__no_type_check__', None):
return {}
if globalns is None:
globalns = getattr(obj, '__globals__', {})
if localns is None:
localns = globalns
elif localns is None:
localns = globalns
defaults = _get_defaults(obj)
hints = dict(obj.__annotations__)
for name, value in hints.items():
if isinstance(value, str):
value = _ForwardRef(value)
value = _eval_type(value, globalns, localns)
if name in defaults and defaults[name] is None:
value = Optional[value]
hints[name] = value
return hints return hints
hints = getattr(obj, '__annotations__', None)
if hints is None:
# Return empty annotations for something that _could_ have them.
if (isinstance(obj, types.FunctionType) or
isinstance(obj, types.BuiltinFunctionType) or
isinstance(obj, types.MethodType) or
isinstance(obj, types.ModuleType)):
return {}
else:
raise TypeError('{!r} is not a module, class, method, '
'or function.'.format(obj))
defaults = _get_defaults(obj)
hints = dict(hints)
for name, value in hints.items():
if value is None:
value = type(None)
if isinstance(value, str):
value = _ForwardRef(value)
value = _eval_type(value, globalns, localns)
if name in defaults and defaults[name] is None:
value = Optional[value]
hints[name] = value
return hints
def no_type_check(arg): def no_type_check(arg):
...@@ -2160,7 +2098,7 @@ class TextIO(IO[str]): ...@@ -2160,7 +2098,7 @@ class TextIO(IO[str]):
pass pass
@abstractproperty @abstractproperty
def errors(self) -> str: def errors(self) -> Optional[str]:
pass pass
@abstractproperty @abstractproperty
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment