##// END OF EJS Templates
Support completion based on type annotations of calls (#14185)...
Matthias Bussonnier -
r28443:b7c9c366 merge
parent child Browse files
Show More
@@ -1,3 +1,4 b''
1 from inspect import signature, Signature
1 from typing import (
2 from typing import (
2 Any,
3 Any,
3 Callable,
4 Callable,
@@ -335,6 +336,7 b' class _IdentitySubscript:'
335
336
336 IDENTITY_SUBSCRIPT = _IdentitySubscript()
337 IDENTITY_SUBSCRIPT = _IdentitySubscript()
337 SUBSCRIPT_MARKER = "__SUBSCRIPT_SENTINEL__"
338 SUBSCRIPT_MARKER = "__SUBSCRIPT_SENTINEL__"
339 UNKNOWN_SIGNATURE = Signature()
338
340
339
341
340 class GuardRejection(Exception):
342 class GuardRejection(Exception):
@@ -415,6 +417,10 b' UNARY_OP_DUNDERS: Dict[Type[ast.unaryop], Tuple[str, ...]] = {'
415 }
417 }
416
418
417
419
420 class Duck:
421 """A dummy class used to create objects of other classes without calling their ``__init__``"""
422
423
418 def _find_dunder(node_op, dunders) -> Union[Tuple[str, ...], None]:
424 def _find_dunder(node_op, dunders) -> Union[Tuple[str, ...], None]:
419 dunder = None
425 dunder = None
420 for op, candidate_dunder in dunders.items():
426 for op, candidate_dunder in dunders.items():
@@ -584,6 +590,27 b' def eval_node(node: Union[ast.AST, None], context: EvaluationContext):'
584 if policy.can_call(func) and not node.keywords:
590 if policy.can_call(func) and not node.keywords:
585 args = [eval_node(arg, context) for arg in node.args]
591 args = [eval_node(arg, context) for arg in node.args]
586 return func(*args)
592 return func(*args)
593 try:
594 sig = signature(func)
595 except ValueError:
596 sig = UNKNOWN_SIGNATURE
597 # if annotation was not stringized, or it was stringized
598 # but resolved by signature call we know the return type
599 not_empty = sig.return_annotation is not Signature.empty
600 not_stringized = not isinstance(sig.return_annotation, str)
601 if not_empty and not_stringized:
602 duck = Duck()
603 # if allow-listed builtin is on type annotation, instantiate it
604 if policy.can_call(sig.return_annotation) and not node.keywords:
605 args = [eval_node(arg, context) for arg in node.args]
606 return sig.return_annotation(*args)
607 try:
608 # if custom class is in type annotation, mock it;
609 # this only works for heap types, not builtins
610 duck.__class__ = sig.return_annotation
611 return duck
612 except TypeError:
613 pass
587 raise GuardRejection(
614 raise GuardRejection(
588 "Call for",
615 "Call for",
589 func, # not joined to avoid calling `repr`
616 func, # not joined to avoid calling `repr`
@@ -253,16 +253,36 b' def test_method_descriptor():'
253 assert guarded_eval("list.copy.__name__", context) == "copy"
253 assert guarded_eval("list.copy.__name__", context) == "copy"
254
254
255
255
256 class HeapType:
257 pass
258
259
260 class CallCreatesHeapType:
261 def __call__(self) -> HeapType:
262 return HeapType()
263
264
265 class CallCreatesBuiltin:
266 def __call__(self) -> frozenset:
267 return frozenset()
268
269
256 @pytest.mark.parametrize(
270 @pytest.mark.parametrize(
257 "data,good,bad,expected",
271 "data,good,bad,expected, equality",
258 [
272 [
259 [[1, 2, 3], "data.index(2)", "data.append(4)", 1],
273 [[1, 2, 3], "data.index(2)", "data.append(4)", 1, True],
260 [{"a": 1}, "data.keys().isdisjoint({})", "data.update()", True],
274 [{"a": 1}, "data.keys().isdisjoint({})", "data.update()", True, True],
275 [CallCreatesHeapType(), "data()", "data.__class__()", HeapType, False],
276 [CallCreatesBuiltin(), "data()", "data.__class__()", frozenset, False],
261 ],
277 ],
262 )
278 )
263 def test_evaluates_calls(data, good, bad, expected):
279 def test_evaluates_calls(data, good, bad, expected, equality):
264 context = limited(data=data)
280 context = limited(data=data)
265 assert guarded_eval(good, context) == expected
281 value = guarded_eval(good, context)
282 if equality:
283 assert value == expected
284 else:
285 assert isinstance(value, expected)
266
286
267 with pytest.raises(GuardRejection):
287 with pytest.raises(GuardRejection):
268 guarded_eval(bad, context)
288 guarded_eval(bad, context)
@@ -534,7 +554,7 b' def test_unbind_method():'
534 def test_assumption_instance_attr_do_not_matter():
554 def test_assumption_instance_attr_do_not_matter():
535 """This is semi-specified in Python documentation.
555 """This is semi-specified in Python documentation.
536
556
537 However, since the specification says 'not guaranted
557 However, since the specification says 'not guaranteed
538 to work' rather than 'is forbidden to work', future
558 to work' rather than 'is forbidden to work', future
539 versions could invalidate this assumptions. This test
559 versions could invalidate this assumptions. This test
540 is meant to catch such a change if it ever comes true.
560 is meant to catch such a change if it ever comes true.
General Comments 0
You need to be logged in to leave comments. Login now