##// END OF EJS Templates
Support for `Self`, custom types, type aliases...
krassowski -
Show More
@@ -2,14 +2,21 b' from inspect import isclass, signature, Signature'
2 2 from typing import (
3 3 Callable,
4 4 Dict,
5 Literal,
6 NamedTuple,
7 NewType,
5 8 Set,
6 9 Sequence,
7 10 Tuple,
8 NamedTuple,
9 11 Type,
10 Literal,
12 Protocol,
11 13 Union,
12 TYPE_CHECKING,
14 get_args,
15 get_origin,
16 )
17 from typing_extensions import (
18 Self, # Python >=3.10
19 TypeAliasType, # Python >=3.12
13 20 )
14 21 import ast
15 22 import builtins
@@ -20,15 +27,8 b' from functools import cached_property'
20 27 from dataclasses import dataclass, field
21 28 from types import MethodDescriptorType, ModuleType
22 29
23 from IPython.utils.docs import GENERATING_DOCUMENTATION
24 from IPython.utils.decorators import undoc
25
26 30
27 if TYPE_CHECKING or GENERATING_DOCUMENTATION:
28 from typing_extensions import Protocol
29 else:
30 # do not require on runtime
31 Protocol = object # requires Python >=3.8
31 from IPython.utils.decorators import undoc
32 32
33 33
34 34 @undoc
@@ -557,7 +557,7 b' def eval_node(node: Union[ast.AST, None], context: EvaluationContext):'
557 557 f" not allowed in {context.evaluation} mode",
558 558 )
559 559 if isinstance(node, ast.Name):
560 return _eval_node_name(node.id, policy, context)
560 return _eval_node_name(node.id, context)
561 561 if isinstance(node, ast.Attribute):
562 562 value = eval_node(node.value, context)
563 563 if policy.can_get_attr(value, node.attr):
@@ -583,14 +583,12 b' def eval_node(node: Union[ast.AST, None], context: EvaluationContext):'
583 583 # or `my_instance.__class__()` - in both cases `func` is `MyClass`.
584 584 # Should return `MyClass` if `__new__` is not overridden,
585 585 # otherwise whatever `__new__` return type is.
586 overridden_return_type = _eval_return_type(
587 func.__new__, policy, node, context
588 )
586 overridden_return_type = _eval_return_type(func.__new__, node, context)
589 587 if overridden_return_type is not NOT_EVALUATED:
590 588 return overridden_return_type
591 return _create_duck_for_type(func)
589 return _create_duck_for_heap_type(func)
592 590 else:
593 return_type = _eval_return_type(func, policy, node, context)
591 return_type = _eval_return_type(func, node, context)
594 592 if return_type is not NOT_EVALUATED:
595 593 return return_type
596 594 raise GuardRejection(
@@ -601,9 +599,7 b' def eval_node(node: Union[ast.AST, None], context: EvaluationContext):'
601 599 raise ValueError("Unhandled node", ast.dump(node))
602 600
603 601
604 def _eval_return_type(
605 func: Callable, policy: EvaluationPolicy, node: ast.Call, context: EvaluationContext
606 ):
602 def _eval_return_type(func: Callable, node: ast.Call, context: EvaluationContext):
607 603 """Evaluate return type of a given callable function.
608 604
609 605 Returns the built-in type, a duck or NOT_EVALUATED sentinel.
@@ -618,20 +614,27 b' def _eval_return_type('
618 614 stringized = isinstance(sig.return_annotation, str)
619 615 if not_empty:
620 616 return_type = (
621 _eval_node_name(sig.return_annotation, policy, context)
617 _eval_node_name(sig.return_annotation, context)
622 618 if stringized
623 619 else sig.return_annotation
624 620 )
625 # if allow-listed builtin is on type annotation, instantiate it
626 if policy.can_call(return_type) and not node.keywords:
627 args = [eval_node(arg, context) for arg in node.args]
628 # if custom class is in type annotation, mock it;
629 return return_type(*args)
630 return _create_duck_for_type(return_type)
621 if return_type is Self and hasattr(func, "__self__"):
622 return func.__self__
623 elif get_origin(return_type) is Literal:
624 type_args = get_args(return_type)
625 if len(type_args) == 1:
626 return type_args[0]
627 elif isinstance(return_type, NewType):
628 return _eval_or_create_duck(return_type.__supertype__, node, context)
629 elif isinstance(return_type, TypeAliasType):
630 return _eval_or_create_duck(return_type.__value__, node, context)
631 else:
632 return _eval_or_create_duck(return_type, node, context)
631 633 return NOT_EVALUATED
632 634
633 635
634 def _eval_node_name(node_id: str, policy: EvaluationPolicy, context: EvaluationContext):
636 def _eval_node_name(node_id: str, context: EvaluationContext):
637 policy = EVALUATION_POLICIES[context.evaluation]
635 638 if policy.allow_locals_access and node_id in context.locals:
636 639 return context.locals[node_id]
637 640 if policy.allow_globals_access and node_id in context.globals:
@@ -647,7 +650,17 b' def _eval_node_name(node_id: str, policy: EvaluationPolicy, context: EvaluationC'
647 650 raise NameError(f"{node_id} not found in locals, globals, nor builtins")
648 651
649 652
650 def _create_duck_for_type(duck_type):
653 def _eval_or_create_duck(duck_type, node: ast.Call, context: EvaluationContext):
654 policy = EVALUATION_POLICIES[context.evaluation]
655 # if allow-listed builtin is on type annotation, instantiate it
656 if policy.can_call(duck_type) and not node.keywords:
657 args = [eval_node(arg, context) for arg in node.args]
658 return duck_type(*args)
659 # if custom class is in type annotation, mock it
660 return _create_duck_for_heap_type(duck_type)
661
662
663 def _create_duck_for_heap_type(duck_type):
651 664 """Create an imitation of an object of a given type (a duck).
652 665
653 666 Returns the duck or NOT_EVALUATED sentinel if duck could not be created.
@@ -1,5 +1,5 b''
1 1 from contextlib import contextmanager
2 from typing import NamedTuple
2 from typing import NamedTuple, Literal, NewType
3 3 from functools import partial
4 4 from IPython.core.guarded_eval import (
5 5 EvaluationContext,
@@ -7,6 +7,10 b' from IPython.core.guarded_eval import ('
7 7 guarded_eval,
8 8 _unbind_method,
9 9 )
10 from typing_extensions import (
11 Self, # Python >=3.10
12 TypeAliasType, # Python >=3.12
13 )
10 14 from IPython.testing import decorators as dec
11 15 import pytest
12 16
@@ -286,6 +290,35 b' class StringAnnotation:'
286 290 return StringAnnotation()
287 291
288 292
293 CustomIntType = NewType("CustomIntType", int)
294 CustomHeapType = NewType("CustomHeapType", HeapType)
295 IntTypeAlias = TypeAliasType("IntTypeAlias", int)
296 HeapTypeAlias = TypeAliasType("HeapTypeAlias", HeapType)
297
298
299 class SpecialTyping:
300 def custom_int_type(self) -> CustomIntType:
301 return CustomIntType(1)
302
303 def custom_heap_type(self) -> CustomHeapType:
304 return CustomHeapType(HeapType())
305
306 # TODO: remove type:ignore comment once mypy
307 # supports explicit calls to `TypeAliasType`, see:
308 # https://github.com/python/mypy/issues/16614
309 def int_type_alias(self) -> IntTypeAlias: # type:ignore[valid-type]
310 return 1
311
312 def heap_type_alias(self) -> HeapTypeAlias: # type:ignore[valid-type]
313 return 1
314
315 def literal(self) -> Literal[False]:
316 return False
317
318 def self(self) -> Self:
319 return self
320
321
289 322 @pytest.mark.parametrize(
290 323 "data,good,expected,equality",
291 324 [
@@ -300,6 +333,13 b' class StringAnnotation:'
300 333 [HeapType, "data()", HeapType, False],
301 334 [InitReturnsFrozenset, "data()", frozenset, False],
302 335 [HeapType(), "data.__class__()", HeapType, False],
336 # supported special cases for typing
337 [SpecialTyping(), "data.custom_int_type()", int, False],
338 [SpecialTyping(), "data.custom_heap_type()", HeapType, False],
339 [SpecialTyping(), "data.int_type_alias()", int, False],
340 [SpecialTyping(), "data.heap_type_alias()", HeapType, False],
341 [SpecialTyping(), "data.self()", SpecialTyping, False],
342 [SpecialTyping(), "data.literal()", False, True],
303 343 # test cases for static methods
304 344 [HasStaticMethod, "data.static_method()", HeapType, False],
305 345 ],
@@ -32,7 +32,7 b' dependencies = ['
32 32 "pygments>=2.4.0",
33 33 "stack_data",
34 34 "traitlets>=5.13.0",
35 "typing_extensions; python_version<'3.10'",
35 "typing_extensions; python_version<'3.12'",
36 36 ]
37 37 dynamic = ["authors", "license", "version"]
38 38
General Comments 0
You need to be logged in to leave comments. Login now