Show More
@@ -600,8 +600,17 def eval_node(node: Union[ast.AST, None], context: EvaluationContext): | |||||
600 | not_stringized = not isinstance(sig.return_annotation, str) |
|
600 | not_stringized = not isinstance(sig.return_annotation, str) | |
601 | if not_empty and not_stringized: |
|
601 | if not_empty and not_stringized: | |
602 | duck = Duck() |
|
602 | duck = Duck() | |
603 | duck.__class__ = sig.return_annotation |
|
603 | # if allow-listed builtin is on type annotation, instantiate it | |
604 | return duck |
|
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 heat types, not builtins | |||
|
610 | duck.__class__ = sig.return_annotation | |||
|
611 | return duck | |||
|
612 | except TypeError: | |||
|
613 | pass | |||
605 | raise GuardRejection( |
|
614 | raise GuardRejection( | |
606 | "Call for", |
|
615 | "Call for", | |
607 | func, # not joined to avoid calling `repr` |
|
616 | func, # not joined to avoid calling `repr` |
@@ -253,16 +253,36 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 |
|
|
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 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