-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support assignment expressions (i.e. the ":=" operator) (#195)
* Support assignment expressions (i.e. the ":=" operator * make flake8 happy * add tests for `NamedExpr` support * make `isort` happy * check that "private" variables cannot be used as target * use version detection support from `_compat`; get rid of `util` * make `isort` happy * move `test_NamedExpr` out of `src/RestrictedPython/tests` to `tests` Co-authored-by: Jens Vagelpohl <jens@netz.ooo> Co-authored-by: Michael Howitz <mh@gocept.com>
- Loading branch information
1 parent
9d0a217
commit 722800e
Showing
3 changed files
with
77 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
"""Assignment expression (``NamedExpr``) tests.""" | ||
|
||
|
||
from ast import NodeTransformer | ||
from ast import parse | ||
from RestrictedPython import compile_restricted | ||
from RestrictedPython import safe_globals | ||
from RestrictedPython._compat import IS_PY38_OR_GREATER | ||
from unittest import skipUnless | ||
from unittest import TestCase | ||
|
||
|
||
@skipUnless(IS_PY38_OR_GREATER, "Feature available for Python 3.8+") | ||
class TestNamedExpr(TestCase): | ||
def test_works(self): | ||
code, gs = compile_str("if x:= x + 1: True\n") | ||
gs["x"] = 0 | ||
exec(code, gs) | ||
self.assertEqual(gs["x"], 1) | ||
|
||
def test_no_private_target(self): | ||
with self.assertRaises(SyntaxError): | ||
compile_str("if _x_:= 1: True\n") | ||
|
||
def test_simple_only(self): | ||
# we test here that only a simple variable is allowed | ||
# as assignemt expression target | ||
# Currently (Python 3.8, 3.9), this is enforced by the | ||
# Python concrete syntax; therefore, some (``ast``) trickery is | ||
# necessary to produce a test for it. | ||
class TransformNamedExprTarget(NodeTransformer): | ||
def visit_NamedExpr(self, node): | ||
# this is brutal but sufficient for the test | ||
node.target = None | ||
return node | ||
|
||
mod = parse("if x:= x + 1: True\n") | ||
mod = TransformNamedExprTarget().visit(mod) | ||
with self.assertRaisesRegex( | ||
SyntaxError, | ||
"Assignment expressions are only allowed for simple target"): | ||
code, gs = compile_str(mod) | ||
|
||
|
||
def compile_str(s, name="<unknown>"): | ||
"""code and globals for *s*. | ||
*s* must be acceptable for ``compile_restricted`` (this is (especially) the | ||
case for an ``str`` or ``ast.Module``). | ||
*name* is a ``str`` used in error messages. | ||
""" | ||
code = compile_restricted(s, name, 'exec') | ||
gs = safe_globals.copy() | ||
gs["__debug__"] = True # assert active | ||
return code, gs |