Skip to content

Commit

Permalink
Handle the case of built-ins, add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
krassowski committed Sep 30, 2023
1 parent 1ed493b commit 3d06e9f
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 8 deletions.
13 changes: 11 additions & 2 deletions IPython/core/guarded_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,8 +600,17 @@ def eval_node(node: Union[ast.AST, None], context: EvaluationContext):
not_stringized = not isinstance(sig.return_annotation, str)
if not_empty and not_stringized:
duck = Duck()
duck.__class__ = sig.return_annotation
return duck
# if allow-listed builtin is on type annotation, instantiate it
if policy.can_call(sig.return_annotation) and not node.keywords:
args = [eval_node(arg, context) for arg in node.args]
return sig.return_annotation(*args)
try:
# if custom class is in type annotation, mock it;
# this only works for heat types, not builtins
duck.__class__ = sig.return_annotation
return duck
except TypeError:
pass
raise GuardRejection(
"Call for",
func, # not joined to avoid calling `repr`
Expand Down
32 changes: 26 additions & 6 deletions IPython/core/tests/test_guarded_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,16 +253,36 @@ def test_method_descriptor():
assert guarded_eval("list.copy.__name__", context) == "copy"


class HeapType:
pass


class CallCreatesHeapType:
def __call__(self) -> HeapType:
return HeapType()


class CallCreatesBuiltin:
def __call__(self) -> frozenset:
return frozenset()


@pytest.mark.parametrize(
"data,good,bad,expected",
"data,good,bad,expected, equality",
[
[[1, 2, 3], "data.index(2)", "data.append(4)", 1],
[{"a": 1}, "data.keys().isdisjoint({})", "data.update()", True],
[[1, 2, 3], "data.index(2)", "data.append(4)", 1, True],
[{"a": 1}, "data.keys().isdisjoint({})", "data.update()", True, True],
[CallCreatesHeapType(), "data()", "data.__class__()", HeapType, False],
[CallCreatesBuiltin(), "data()", "data.__class__()", frozenset, False],
],
)
def test_evaluates_calls(data, good, bad, expected):
def test_evaluates_calls(data, good, bad, expected, equality):
context = limited(data=data)
assert guarded_eval(good, context) == expected
value = guarded_eval(good, context)
if equality:
assert value == expected
else:
assert isinstance(value, expected)

with pytest.raises(GuardRejection):
guarded_eval(bad, context)
Expand Down Expand Up @@ -534,7 +554,7 @@ def index(self, k):
def test_assumption_instance_attr_do_not_matter():
"""This is semi-specified in Python documentation.
However, since the specification says 'not guaranted
However, since the specification says 'not guaranteed
to work' rather than 'is forbidden to work', future
versions could invalidate this assumptions. This test
is meant to catch such a change if it ever comes true.
Expand Down

0 comments on commit 3d06e9f

Please sign in to comment.