Fix #1790: Rework statements in while condition

This avoids compiling them more than once while also applying some simplification.
This commit is contained in:
Ryan Gonzalez 2019-07-07 18:53:22 -05:00 committed by Kodi Arfer
parent 3a4e31c209
commit 289f172d56
3 changed files with 47 additions and 17 deletions

View File

@ -12,6 +12,7 @@ Removals
Bug Fixes Bug Fixes
------------------------------ ------------------------------
* Statements in the second argument of `assert` are now executed. * Statements in the second argument of `assert` are now executed.
* Fixed the expression of a while loop that contains statements being compiled twice.
0.17.0 0.17.0
============================== ==============================

View File

@ -1373,33 +1373,46 @@ class HyASTCompiler(object):
def compile_while_expression(self, expr, root, cond, body, else_expr): def compile_while_expression(self, expr, root, cond, body, else_expr):
cond_compiled = self.compile(cond) cond_compiled = self.compile(cond)
body = self._compile_branch(body)
body += body.expr_as_stmt()
body_stmts = body.stmts or [asty.Pass(expr)]
if cond_compiled.stmts: if cond_compiled.stmts:
# 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, i.e.:
cond_var = self.get_anon_var() # anon_var = True
return self.compile(mkexpr( # while anon_var:
'do', # condition stmts...
mkexpr('setv', cond_var, 'True'), # anon_var = condition expr
mkexpr('while', cond_var, # if anon_var:
# Cast the condition to a bool in case it's mutable and # while loop body
# changes its truth value, but use (not (not ...)) instead of cond_var = asty.Name(cond, id=self.get_anon_var(), ctx=ast.Load())
# `bool` in case `bool` has been redefined. def make_not(operand):
mkexpr('setv', cond_var, mkexpr('not', mkexpr('not', [cond]))), return asty.UnaryOp(cond, op=ast.Not(), operand=operand)
mkexpr('if*', cond_var, mkexpr('do', rest=body)),
*([mkexpr('else', rest=else_expr)] if else_expr is not None else []))).replace(expr)) # noqa body_stmts = cond_compiled.stmts + [
asty.Assign(cond, targets=[self._storeize(cond, cond_var)],
# Cast the condition to a bool in case it's mutable and
# changes its truth value, but use (not (not ...)) instead of
# `bool` in case `bool` has been redefined.
value=make_not(make_not(cond_compiled.force_expr))),
asty.If(cond, test=cond_var, body=body_stmts, orelse=[]),
]
cond_compiled = (Result()
+ asty.Assign(cond, targets=[self._storeize(cond, cond_var)],
value=asty.Name(cond, id="True", ctx=ast.Load()))
+ cond_var)
orel = Result() orel = Result()
if else_expr is not None: if else_expr is not None:
orel = self._compile_branch(else_expr) orel = self._compile_branch(else_expr)
orel += orel.expr_as_stmt() orel += orel.expr_as_stmt()
body = self._compile_branch(body)
body += body.expr_as_stmt()
ret = cond_compiled + asty.While( ret = cond_compiled + asty.While(
expr, test=cond_compiled.force_expr, expr, test=cond_compiled.force_expr,
body=body.stmts or [asty.Pass(expr)], body=body_stmts,
orelse=orel.stmts) orelse=orel.stmts)
return ret return ret

View File

@ -197,7 +197,23 @@
(.append l 1) (.append l 1)
(len l)) (len l))
(while (!= (f) 4) (do)) (while (!= (f) 4) (do))
(assert (= l [1 1 1 1]))) (assert (= l [1 1 1 1]))
; only compile the condition once
; https://github.com/hylang/hy/issues/1790
(global while-cond-var)
(setv while-cond-var 10)
(eval
'(do
(defmacro while-cond []
(global while-cond-var)
(assert (= while-cond-var 10))
(+= while-cond-var 1)
`(do
(setv x 3)
False))
(while (while-cond))
(assert (= x 3)))))
(defn test-while-loop-else [] (defn test-while-loop-else []
(setv count 5) (setv count 5)