Skip to content

Commit 68c86c0

Browse files
committed
fix local/delete incorrect use detection at macro expansion time
1 parent db75cce commit 68c86c0

File tree

2 files changed

+23
-14
lines changed

2 files changed

+23
-14
lines changed

unpythonic/syntax/__init__.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -422,9 +422,9 @@ def let(tree, *, args, syntax, expander, **kw): # noqa: F811
422422
if syntax != "expr":
423423
raise SyntaxError("let is an expr macro only")
424424

425-
tree = expander.visit(tree)
426-
427-
return _destructure_and_apply_let(tree, args, expander, _let)
425+
# The `let[]` family of macros expands inside out.
426+
with dyn.let(_macro_expander=expander):
427+
return _destructure_and_apply_let(tree, args, expander, _let)
428428

429429
@parametricmacro
430430
def letseq(tree, *, args, syntax, expander, **kw): # noqa: F811
@@ -438,9 +438,8 @@ def letseq(tree, *, args, syntax, expander, **kw): # noqa: F811
438438
if syntax != "expr":
439439
raise SyntaxError("letseq is an expr macro only")
440440

441-
tree = expander.visit(tree)
442-
443-
return _destructure_and_apply_let(tree, args, expander, _letseq)
441+
with dyn.let(_macro_expander=expander):
442+
return _destructure_and_apply_let(tree, args, expander, _letseq)
444443

445444
@parametricmacro
446445
def letrec(tree, *, args, syntax, expander, **kw): # noqa: F811
@@ -459,9 +458,8 @@ def letrec(tree, *, args, syntax, expander, **kw): # noqa: F811
459458
if syntax != "expr":
460459
raise SyntaxError("letrec is an expr macro only")
461460

462-
tree = expander.visit(tree)
463-
464-
return _destructure_and_apply_let(tree, args, expander, _letrec)
461+
with dyn.let(_macro_expander=expander):
462+
return _destructure_and_apply_let(tree, args, expander, _letrec)
465463

466464
# NOTE: At the macro interface, the invocations `let()[...]` (empty args)
467465
# and `let[...]` (no args) were indistinguishable in MacroPy. This was a

unpythonic/syntax/letdo.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ def _letimpl(bindings, body, mode):
5858
"""bindings: sequence of ast.Tuple: (k1, v1), (k2, v2), ..., (kn, vn)"""
5959
assert mode in ("let", "letrec")
6060

61+
# The let constructs are currently inside-out macros; expand other macro
62+
# invocations in both bindings and body.
63+
#
64+
# But apply the implicit `do` (extra bracket syntax) first.
6165
body = implicit_do(body)
66+
body = dyn._macro_expander.visit(body)
6267
if not bindings:
6368
# Optimize out a `let` with no bindings. The macro layer cannot trigger
6469
# this case, because our syntaxes always require at least one binding.
@@ -81,6 +86,8 @@ def _letimpl(bindings, body, mode):
8186
# `let[(...) in ...]`, `let[..., where(...)]` - and in these cases,
8287
# both the bindings and the body reside inside the brackets.
8388
return body # pragma: no cover
89+
bindings = dyn._macro_expander.visit(bindings)
90+
8491
names, values = zip(*[b.elts for b in bindings]) # --> (k1, ..., kn), (v1, ..., vn)
8592
names = [k.id for k in names] # any duplicates will be caught by env at run-time
8693

@@ -327,20 +334,24 @@ class UnpythonicDoDeleteMarker(UnpythonicLetDoMarker):
327334
# TODO: fail-fast: promote `local[]`/`delete[]` usage errors to compile-time errors
328335
# TODO: (doesn't currently work e.g. for `let` with an implicit do (extra bracket notation))
329336
def local(tree): # syntax transformer
330-
# if _do_level.value < 1:
331-
# raise SyntaxError("local[] is only valid within a do[] or do0[]") # pragma: no cover
337+
if _do_level.value < 1:
338+
raise SyntaxError("local[] is only valid within a do[] or do0[]") # pragma: no cover
332339
return UnpythonicDoLocalMarker(tree)
333340

334341
def delete(tree): # syntax transformer
335-
# if _do_level.value < 1:
336-
# raise SyntaxError("delete[] is only valid within a do[] or do0[]") # pragma: no cover
342+
if _do_level.value < 1:
343+
raise SyntaxError("delete[] is only valid within a do[] or do0[]") # pragma: no cover
337344
return UnpythonicDoDeleteMarker(tree)
338345

339346
def do(tree):
340347
if type(tree) not in (Tuple, List):
341348
raise SyntaxError("do body: expected a sequence of comma-separated expressions") # pragma: no cover, let's not test the macro expansion errors.
342349

343-
# Handle nested `local[]`/`delete[]`.
350+
# Handle nested `local[]`/`delete[]`. This will also expand any other nested macro invocations.
351+
# TODO: If we want to make `do` an outside-in macro, instantiate another expander here and register
352+
# TODO: only the `local` and `delete` transformers to it - grabbing them from the current expander's
353+
# TODO: bindings to respect as-imports. (Expander instances are cheap in `mcpyrate`.)
354+
# TODO: Grep the `unpythonic` codebase (and `mcpyrate` demos) for `MacroExpander` to see how.
344355
with _do_level.changed_by(+1):
345356
tree = dyn._macro_expander.visit(tree)
346357

0 commit comments

Comments
 (0)