Skip to content

Commit 7d1d925

Browse files
authored
Fix defining async generators (#1225)
Fixes #1180
1 parent 55dfdfc commit 7d1d925

File tree

5 files changed

+170
-104
lines changed

5 files changed

+170
-104
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88
### Changed
9-
* Single arity functions can be tagged with `^:allow-unsafe-names` to preserve their parameter names (#1212)
9+
* Single arity functions can be tagged with `^:allow-unsafe-names` to preserve their parameter names (#1212)
10+
11+
### Fixed
12+
* Fix an issue where the compiler would generate an illegal `return` statement for asynchronous generators (#1180)
1013

1114
## [v0.3.7]
1215
### Fixed

src/basilisp/lang/compiler/analyzer.py

Lines changed: 70 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@
1010
import sys
1111
import uuid
1212
from collections import defaultdict
13-
from collections.abc import Collection, Iterable, Mapping, MutableMapping, MutableSet
13+
from collections.abc import (
14+
Collection,
15+
Iterable,
16+
Iterator,
17+
Mapping,
18+
MutableMapping,
19+
MutableSet,
20+
)
1421
from datetime import datetime
1522
from decimal import Decimal
1623
from fractions import Fraction
@@ -89,6 +96,7 @@
8996
Fn,
9097
FnArity,
9198
FunctionContext,
99+
FunctionContextType,
92100
HostCall,
93101
HostField,
94102
If,
@@ -447,16 +455,23 @@ def is_async_ctx(self) -> bool:
447455
448456
It is possible that the current function is defined inside other functions,
449457
so this does not imply anything about the nesting level of the current node."""
450-
return self.func_ctx == FunctionContext.ASYNC_FUNCTION
458+
func_ctx = self.func_ctx
459+
return (
460+
func_ctx is not None
461+
and func_ctx.function_type == FunctionContextType.ASYNC_FUNCTION
462+
)
451463

452464
@contextlib.contextmanager
453-
def new_func_ctx(self, context_type: FunctionContext):
465+
def new_func_ctx(
466+
self, context_type: FunctionContextType
467+
) -> Iterator[FunctionContext]:
454468
"""Context manager which can be used to set a function or method context for
455469
child nodes to examine. A new function context is pushed onto the stack each
456470
time the Analyzer finds a new function or method definition, so there may be
457471
many nested function contexts."""
458-
self._func_ctx.append(context_type)
459-
yield
472+
func_ctx = FunctionContext(context_type)
473+
self._func_ctx.append(func_ctx)
474+
yield func_ctx
460475
self._func_ctx.pop()
461476

462477
@property
@@ -1186,7 +1201,7 @@ def __deftype_method_param_bindings(
11861201
return has_vargs, fixed_arity, param_nodes
11871202

11881203

1189-
def __deftype_classmethod(
1204+
def __deftype_classmethod( # pylint: disable=too-many-locals
11901205
form: Union[llist.PersistentList, ISeq],
11911206
ctx: AnalyzerContext,
11921207
method_name: str,
@@ -1222,24 +1237,23 @@ def __deftype_classmethod(
12221237
has_vargs, fixed_arity, param_nodes = __deftype_method_param_bindings(
12231238
params, ctx, SpecialForm.DEFTYPE
12241239
)
1225-
with ctx.new_func_ctx(FunctionContext.CLASSMETHOD), ctx.expr_pos():
1240+
with ctx.new_func_ctx(FunctionContextType.CLASSMETHOD), ctx.expr_pos():
12261241
stmts, ret = _body_ast(runtime.nthrest(form, 2), ctx)
1242+
body = Do(
1243+
form=form.rest,
1244+
statements=vec.vector(stmts),
1245+
ret=ret,
1246+
is_body=True,
1247+
env=ctx.get_node_env(),
1248+
)
12271249
method = DefTypeClassMethod(
12281250
form=form,
12291251
name=method_name,
12301252
params=vec.vector(param_nodes),
12311253
fixed_arity=fixed_arity,
12321254
is_variadic=has_vargs,
12331255
kwarg_support=kwarg_support,
1234-
body=Do(
1235-
form=form.rest,
1236-
statements=vec.vector(stmts),
1237-
ret=ret,
1238-
is_body=True,
1239-
# Use the argument vector or first body statement, whichever
1240-
# exists, for metadata.
1241-
env=ctx.get_node_env(),
1242-
),
1256+
body=body,
12431257
class_local=cls_binding,
12441258
env=ctx.get_node_env(),
12451259
)
@@ -1286,8 +1300,15 @@ def __deftype_or_reify_method( # pylint: disable=too-many-arguments,too-many-lo
12861300

12871301
loop_id = genname(method_name)
12881302
with ctx.new_recur_point(loop_id, param_nodes):
1289-
with ctx.new_func_ctx(FunctionContext.METHOD), ctx.expr_pos():
1303+
with ctx.new_func_ctx(FunctionContextType.METHOD), ctx.expr_pos():
12901304
stmts, ret = _body_ast(runtime.nthrest(form, 2), ctx)
1305+
body = Do(
1306+
form=form.rest,
1307+
statements=vec.vector(stmts),
1308+
ret=ret,
1309+
is_body=True,
1310+
env=ctx.get_node_env(),
1311+
)
12911312
method = DefTypeMethodArity(
12921313
form=form,
12931314
name=method_name,
@@ -1296,15 +1317,7 @@ def __deftype_or_reify_method( # pylint: disable=too-many-arguments,too-many-lo
12961317
fixed_arity=fixed_arity,
12971318
is_variadic=has_vargs,
12981319
kwarg_support=kwarg_support,
1299-
body=Do(
1300-
form=form.rest,
1301-
statements=vec.vector(stmts),
1302-
ret=ret,
1303-
is_body=True,
1304-
# Use the argument vector or first body statement, whichever
1305-
# exists, for metadata.
1306-
env=ctx.get_node_env(),
1307-
),
1320+
body=body,
13081321
loop_id=loop_id,
13091322
env=ctx.get_node_env(),
13101323
)
@@ -1356,22 +1369,21 @@ def __deftype_or_reify_property(
13561369

13571370
assert not has_vargs, f"{special_form} properties may not have arguments"
13581371

1359-
with ctx.new_func_ctx(FunctionContext.PROPERTY), ctx.expr_pos():
1372+
with ctx.new_func_ctx(FunctionContextType.PROPERTY), ctx.expr_pos():
13601373
stmts, ret = _body_ast(runtime.nthrest(form, 2), ctx)
1361-
prop = DefTypeProperty(
1362-
form=form,
1363-
name=method_name,
1364-
this_local=this_binding,
1365-
params=vec.vector(param_nodes),
1366-
body=Do(
1374+
body = Do(
13671375
form=form.rest,
13681376
statements=vec.vector(stmts),
13691377
ret=ret,
13701378
is_body=True,
1371-
# Use the argument vector or first body statement, whichever
1372-
# exists, for metadata.
13731379
env=ctx.get_node_env(),
1374-
),
1380+
)
1381+
prop = DefTypeProperty(
1382+
form=form,
1383+
name=method_name,
1384+
this_local=this_binding,
1385+
params=vec.vector(param_nodes),
1386+
body=body,
13751387
env=ctx.get_node_env(),
13761388
)
13771389
prop.visit(partial(_assert_no_recur, ctx))
@@ -1393,24 +1405,23 @@ def __deftype_staticmethod(
13931405
has_vargs, fixed_arity, param_nodes = __deftype_method_param_bindings(
13941406
args, ctx, SpecialForm.DEFTYPE
13951407
)
1396-
with ctx.new_func_ctx(FunctionContext.STATICMETHOD), ctx.expr_pos():
1408+
with ctx.new_func_ctx(FunctionContextType.STATICMETHOD), ctx.expr_pos():
13971409
stmts, ret = _body_ast(runtime.nthrest(form, 2), ctx)
1410+
body = Do(
1411+
form=form.rest,
1412+
statements=vec.vector(stmts),
1413+
ret=ret,
1414+
is_body=True,
1415+
env=ctx.get_node_env(),
1416+
)
13981417
method = DefTypeStaticMethod(
13991418
form=form,
14001419
name=method_name,
14011420
params=vec.vector(param_nodes),
14021421
fixed_arity=fixed_arity,
14031422
is_variadic=has_vargs,
14041423
kwarg_support=kwarg_support,
1405-
body=Do(
1406-
form=form.rest,
1407-
statements=vec.vector(stmts),
1408-
ret=ret,
1409-
is_body=True,
1410-
# Use the argument vector or first body statement, whichever
1411-
# exists, for metadata.
1412-
env=ctx.get_node_env(),
1413-
),
1424+
body=body,
14141425
env=ctx.get_node_env(),
14151426
)
14161427
method.visit(partial(_assert_no_recur, ctx))
@@ -2092,31 +2103,28 @@ def __fn_method_ast( # pylint: disable=too-many-locals
20922103
with ctx.new_recur_point(fn_loop_id, param_nodes):
20932104
with (
20942105
ctx.new_func_ctx(
2095-
FunctionContext.ASYNC_FUNCTION
2106+
FunctionContextType.ASYNC_FUNCTION
20962107
if is_async
2097-
else FunctionContext.FUNCTION
2108+
else FunctionContextType.FUNCTION
20982109
),
20992110
ctx.expr_pos(),
21002111
):
21012112
stmts, ret = _body_ast(form.rest, ctx)
2113+
body = Do(
2114+
form=form.rest,
2115+
statements=vec.vector(stmts),
2116+
ret=ret,
2117+
is_body=True,
2118+
env=ctx.get_node_env(),
2119+
)
21022120
method = FnArity(
21032121
form=form,
21042122
loop_id=fn_loop_id,
21052123
params=vec.vector(param_nodes),
21062124
tag=return_tag,
21072125
is_variadic=has_vargs,
21082126
fixed_arity=len(param_nodes) - int(has_vargs),
2109-
body=Do(
2110-
form=form.rest,
2111-
statements=vec.vector(stmts),
2112-
ret=ret,
2113-
is_body=True,
2114-
# Use the argument vector or first body statement, whichever
2115-
# exists, for metadata.
2116-
env=ctx.get_node_env(),
2117-
),
2118-
# Use the argument vector for fetching line/col since the
2119-
# form itself is a sequence with no meaningful metadata.
2127+
body=body,
21202128
env=ctx.get_node_env(),
21212129
)
21222130
method.visit(partial(_assert_recur_is_tail, ctx))
@@ -3420,6 +3428,9 @@ def _yield_ast(form: ISeq, ctx: AnalyzerContext) -> Yield:
34203428
"yield forms must contain 1 or 2 elements, as in: (yield [expr])", form=form
34213429
)
34223430

3431+
# Indicate that the current function is a generator
3432+
ctx.func_ctx.is_generator = True
3433+
34233434
if nelems == 2:
34243435
with ctx.expr_pos():
34253436
expr = _analyze_form(runtime.nth(form, 1), ctx)

src/basilisp/lang/compiler/generator.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
Do,
6161
Fn,
6262
FnArity,
63+
FunctionContextType,
6364
HostCall,
6465
HostField,
6566
If,
@@ -1715,7 +1716,16 @@ def __fn_args_to_py_ast(
17151716

17161717
body_ast = _synthetic_do_to_py_ast(ctx, body)
17171718
fn_body_ast.extend(map(statementize, body_ast.dependencies))
1718-
fn_body_ast.append(ast.Return(value=body_ast.node))
1719+
1720+
func_ctx = body.env.func_ctx
1721+
if (
1722+
func_ctx is not None
1723+
and func_ctx.is_generator
1724+
and func_ctx.function_type == FunctionContextType.ASYNC_FUNCTION
1725+
):
1726+
fn_body_ast.append(statementize(body_ast.node))
1727+
else:
1728+
fn_body_ast.append(ast.Return(value=body_ast.node))
17191729

17201730
return fn_args, varg, fn_body_ast, fn_def_deps
17211731

0 commit comments

Comments
 (0)