Merge pull request #1791 from Kodiologist/assertmsg

Run statements in the second argument of `assert`
This commit is contained in:
Kodi Arfer 2019-07-21 11:19:22 -04:00 committed by GitHub
commit 3a4e31c209
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 32 deletions

View File

@ -9,6 +9,10 @@ Removals
* Support for attribute lists in `defclass` has been removed. Use `setv` * Support for attribute lists in `defclass` has been removed. Use `setv`
and `defn` instead. and `defn` instead.
Bug Fixes
------------------------------
* Statements in the second argument of `assert` are now executed.
0.17.0 0.17.0
============================== ==============================

View File

@ -324,6 +324,18 @@ def is_unpack(kind, x):
and x[0] == "unpack-" + kind) and x[0] == "unpack-" + kind)
def make_hy_model(outer, x, rest):
return outer(
[HySymbol(a) if type(a) is str else
a[0] if type(a) is list else a
for a in x] +
(rest or []))
def mkexpr(*items, **kwargs):
return make_hy_model(HyExpression, items, kwargs.get('rest'))
def mklist(*items, **kwargs):
return make_hy_model(HyList, items, kwargs.get('rest'))
class HyASTCompiler(object): class HyASTCompiler(object):
"""A Hy-to-Python AST compiler""" """A Hy-to-Python AST compiler"""
@ -390,21 +402,11 @@ class HyASTCompiler(object):
ret = Result() ret = Result()
for module, names in self.imports.items(): for module, names in self.imports.items():
if None in names: if None in names:
e = HyExpression([ ret += self.compile(mkexpr('import', module).replace(expr))
HySymbol("import"),
HySymbol(module),
]).replace(expr)
ret += self.compile(e)
names = sorted(name for name in names if name) names = sorted(name for name in names if name)
if names: if names:
e = HyExpression([ ret += self.compile(mkexpr('import',
HySymbol("import"), mklist(module, mklist(*names))))
HyList([
HySymbol(module),
HyList([HySymbol(name) for name in names])
])
]).replace(expr)
ret += self.compile(e)
self.imports = defaultdict(set) self.imports = defaultdict(set)
return ret.stmts return ret.stmts
@ -818,11 +820,22 @@ class HyASTCompiler(object):
@special("assert", [FORM, maybe(FORM)]) @special("assert", [FORM, maybe(FORM)])
def compile_assert_expression(self, expr, root, test, msg): def compile_assert_expression(self, expr, root, test, msg):
if msg is None or type(msg) is HySymbol:
ret = self.compile(test) ret = self.compile(test)
e = ret.force_expr return ret + asty.Assert(
if msg is not None: expr,
msg = self.compile(msg).force_expr test=ret.force_expr,
return ret + asty.Assert(expr, test=e, msg=msg) msg=(None if msg is None else self.compile(msg).force_expr))
# The `msg` part may involve statements, which we only
# want to be executed if the assertion fails. Rewrite the
# form to set `msg` to a variable.
msg_var = self.get_anon_var()
return self.compile(mkexpr(
'if*', mkexpr('and', '__debug__', mkexpr('not', [test])),
mkexpr('do',
mkexpr('setv', msg_var, [msg]),
mkexpr('assert', 'False', msg_var))).replace(expr))
@special(["global", "nonlocal"], [oneplus(SYM)]) @special(["global", "nonlocal"], [oneplus(SYM)])
def compile_global_or_nonlocal(self, expr, root, syms): def compile_global_or_nonlocal(self, expr, root, syms):
@ -1364,19 +1377,17 @@ class HyASTCompiler(object):
# We need to ensure the statements for the condition are # We need to ensure the statements for the condition are
# executed on every iteration. Rewrite the loop to use a # executed on every iteration. Rewrite the loop to use a
# single anonymous variable as the condition. # single anonymous variable as the condition.
def e(*x): return HyExpression(x) cond_var = self.get_anon_var()
s = HySymbol return self.compile(mkexpr(
cond_var = s(self.get_anon_var()) 'do',
return self.compile(e( mkexpr('setv', cond_var, 'True'),
s('do'), mkexpr('while', cond_var,
e(s('setv'), cond_var, 1),
e(s('while'), cond_var,
# Cast the condition to a bool in case it's mutable and # Cast the condition to a bool in case it's mutable and
# changes its truth value, but use (not (not ...)) instead of # changes its truth value, but use (not (not ...)) instead of
# `bool` in case `bool` has been redefined. # `bool` in case `bool` has been redefined.
e(s('setv'), cond_var, e(s('not'), e(s('not'), cond))), mkexpr('setv', cond_var, mkexpr('not', mkexpr('not', [cond]))),
e(s('if*'), cond_var, e(s('do'), *body)), mkexpr('if*', cond_var, mkexpr('do', rest=body)),
*([e(s('else'), *else_expr)] if else_expr is not None else []))).replace(expr)) # noqa *([mkexpr('else', rest=else_expr)] if else_expr is not None else []))).replace(expr)) # noqa
orel = Result() orel = Result()
if else_expr is not None: if else_expr is not None:

View File

@ -1653,16 +1653,15 @@ macros()
(= (identify-keywords 1 "bloo" :foo) (= (identify-keywords 1 "bloo" :foo)
["other" "other" "keyword"]))) ["other" "other" "keyword"])))
#@(pytest.mark.xfail
(defn test-assert-multistatements [] (defn test-assert-multistatements []
; https://github.com/hylang/hy/issues/1390 ; https://github.com/hylang/hy/issues/1390
(setv s (set)) (setv l [])
(defn f [x] (defn f [x]
(.add s x) (.append l x)
False) False)
(with [(pytest.raises AssertionError)] (with [(pytest.raises AssertionError)]
(assert (do (f 1) (f 2)) (do (f 3) (f 4)))) (assert (do (f 1) (f 2)) (do (f 3) (f 4))))
(assert (= s #{1 2 3 4})))) (assert (= l [1 2 3 4])))
(defn test-underscore_variables [] (defn test-underscore_variables []
; https://github.com/hylang/hy/issues/1340 ; https://github.com/hylang/hy/issues/1340