def transform(self, tree): if is_captured_value(tree): return tree # don't recurse! if type(tree) is Name and tree.id == "__ar_": tree.id = our_lambda_argname elif type(tree) is arg and tree.arg == "__ar_": tree.arg = our_lambda_argname return self.generic_visit(tree)
def transform(self, tree): # Ignore hygienically captured values, and don't recurse in them. # In `mcpyrate`, they are represented by Call nodes that match # `mcpyrate.quotes.is_captured_value`. if is_captured_value(tree): return tree hascurry = self.state.hascurry if type(tree) is Call: # Don't auto-curry some calls we know not to need it. This is both a performance optimization # and allows other macros (particularly `lazify`) to be able to see the original calls. # (It also generates cleaner expanded output.) # - `Values(...)` accepts any args and kwargs, so currying it does not make sense. # - `(chain_conts(cc1, cc2))(...)` handles a return value in `with continuations`. # This has the effect that in `with continuations`, the tail-calls to continuation # functions won't be curried, but perhaps that's ok. This allows the Pytkell dialect's # `with lazify, autocurry` combo to work with an inner `with continuations`. if (isx(tree.func, "Values") or (type(tree.func) is Call and isx(tree.func.func, "chain_conts"))): # However, *do* auto-curry in the positional and named args of the call. tree.args = self.visit(tree.args) tree.keywords = self.visit(tree.keywords) return tree else: # general case if has_curry( tree): # detect decorated lambda with manual curry # the lambda inside the curry(...) is the next Lambda node we will descend into. hascurry = True if not isx(tree.func, _iscurry): tree.args = [tree.func] + tree.args tree.func = q[h[currycall]] if hascurry: # this must be done after the edit because the edit changes the children self.generic_withstate(tree, hascurry=True) elif type(tree) in (FunctionDef, AsyncFunctionDef): if not any( isx(item, _iscurry) for item in tree.decorator_list): # no manual curry already k = suggest_decorator_index("curry", tree.decorator_list) if k is not None: tree.decorator_list.insert(k, q[h[curryf]]) else: # couldn't determine insert position; just plonk it at the end and hope for the best tree.decorator_list.append(q[h[curryf]]) elif type(tree) is Lambda: if not hascurry: thelambda = tree tree = q[h[curryf]( a[thelambda] )] # plonk it as innermost, we'll sort them later # don't recurse on the lambda we just moved, but recurse inside it. self.withstate(thelambda.body, hascurry=False) thelambda.body = self.visit(thelambda.body) return tree return self.generic_visit(tree)
def transform(self, tree): if is_captured_value(tree): return tree # don't recurse! expr = isdelete(tree) if expr: if type(expr) is not Name: raise SyntaxError("delete[...] takes exactly one name" ) # pragma: no cover self.collect(expr.id) return q[a[envdel]( u[expr.id])] # `delete[x]` --> `e.pop('x')` return tree # don't recurse!
def examine(self, tree): if is_captured_value(tree): return # don't recurse! elif is_let_syntax(tree) or is_abbrev(tree): return # don't recurse! elif ismatch(tree): # Expand the stray helper macro invocation, to trigger its `SyntaxError` # with a useful message, and *make the expander generate a use site traceback*. # # (If we just `raise` here directly, the expander won't see the use site # of the `with expr` or `with block`, but just that of the `do[]`.) dyn._macro_expander.visit(tree) self.generic_visit(tree)
def transform(self, tree): if is_captured_value(tree): return tree # don't recurse! # Don't recurse into nested `f[]`. # TODO: This would benefit from macro destructuring in the expander. # TODO: See https://github.com/Technologicat/mcpyrate/issues/3 if type(tree) is Subscript and type( tree.value) is Name and tree.value.id in mynames: return tree elif type(tree) is Name and tree.id == "_": name = gensym("_") tree.id = name self.collect(name) return self.generic_visit(tree)
def transform(self, tree): if is_captured_value(tree): return tree # don't recurse! expr = islocaldef(tree) if expr: if not isenvassign(expr): raise SyntaxError( "local[...] takes exactly one expression of the form 'name << value'" ) # pragma: no cover view = UnexpandedEnvAssignView(expr) self.collect(view.name) view.value = self.visit( view.value ) # nested local[] (e.g. from `do0[local[y << 5],]`) return expr # `local[x << 21]` --> `x << 21`; compiling *that* makes the env-assignment occur. return tree # don't recurse!
def transform(self, tree): if is_captured_value(tree): return tree # don't recurse! # we can robustly sort only decorators for which we know the correct ordering. if is_decorated_lambda(tree, mode="known"): decorator_list, thelambda = destructure_decorated_lambda(tree) # We can just swap the func attributes of the nodes. ordered_decorator_list = sorted(decorator_list, key=prioritize) ordered_funcs = [x.func for x in ordered_decorator_list] for thecall, newfunc in zip(decorator_list, ordered_funcs): thecall.func = newfunc # don't recurse on the tail of the "decorator list" (call chain), # but recurse into the lambda body. thelambda.body = self.visit(thelambda.body) return tree return self.generic_visit(tree)
def transform(self, tree): if is_captured_value(tree): return tree # don't recurse! # discard Expr wrapper (identifying a statement position) at use site # when performing a block substitution if mode == "block" and type(tree) is Expr and isthisfunc( tree.value): tree = subst(tree.value) return tree elif isthisfunc(tree): if mode == "block": raise SyntaxError( f"cannot substitute block '{name}' into expression position" ) # pragma: no cover tree = subst(tree) return self.generic_visit(tree) return self.generic_visit(tree)
def transform(self, tree): if is_captured_value(tree): return tree # don't recurse! if type(tree) is Call and type( tree.func) is Name and tree.func.id == pname: names = [q[u[unparse(node)]] for node in tree.args ] # x --> "x"; (1 + 2) --> "(1 + 2)"; ... names = q[t[names]] values = q[t[tree.args]] tree.args = [names, values] # can't use inspect.stack in the printer itself because we want the line number *before macro expansion*. lineno = tree.lineno if hasattr(tree, "lineno") else None tree.keywords += [ keyword(arg="filename", value=q[h[callsite_filename]()]), keyword(arg="lineno", value=q[u[lineno]]) ] tree.func = pfunc return self.generic_visit(tree)
def getname(tree, accept_attr=True): """The cousin of ``isx``. From the same types of trees, extract the name as str. If no match on ``tree``, return ``None``. """ if isinstance(tree, Done): return getname(tree.body, accept_attr=accept_attr) if type(tree) is Name: return tree.id key = is_captured_value(tree) # AST -> (name, frozen_value) or False if key: # TODO: Python 3.8+: use walrus assignment here name, frozen_value = key return name if accept_attr and type(tree) is Attribute: return tree.attr return None
def transform(self, tree): if is_captured_value(tree): return tree # don't recurse! # Respect the boundaries of nested test constructs (don't recurse there). if isunexpandedtestmacro(tree): return tree elif _is_important_subexpr_mark(tree): if sys.version_info >= ( 3, 9, 0): # Python 3.9+: the Index wrapper is gone. thing = tree.slice else: thing = tree.slice.value self.collect( thing ) # or anything really; value not used, we just count them. # Handle any nested the[] subexpressions subtree = self.visit(thing) return _inject_value_recorder(envname, subtree) else: return self.generic_visit(tree) # recurse
def transform(self, tree): if is_captured_value(tree): return tree # don't recurse! def subst(): # Copy just to be on the safe side. Different instances may be # edited differently by other macros expanded later. return deepcopy(value) # discard Expr wrapper (identifying a statement position) at use site # when performing a block substitution if mode == "block" and type(tree) is Expr and isthisname( tree.value): tree = subst() return tree elif isthisname(tree): if mode == "block": raise SyntaxError( f"cannot substitute block '{name}' into expression position" ) # pragma: no cover tree = subst() return self.generic_visit(tree) return self.generic_visit(tree)
def transform(self, tree): if is_captured_value(tree): return tree # don't recurse! if type(tree) is Lambda: tree.body = _implicit_do(tree.body) return self.generic_visit(tree)
def transform(self, tree): if is_captured_value(tree): return tree # don't recurse! bindings = self.state.bindings enames = self.state.enames def isourupdate(thecall): if type(thecall.func) is not Attribute: return False return thecall.func.attr == "update" and any( isx(thecall.func.value, x) for x in enames) if isfunctionoruserlambda(tree): argnames = getargs(tree) if argnames: # prepend env init to function body, update bindings kws = [keyword(arg=k, value=q[n[k]]) for k in argnames] # "x" --> x newbindings = bindings.copy() if type(tree) in (FunctionDef, AsyncFunctionDef): ename = gensym("e") theenv = q[h[_envify]()] theenv.keywords = kws with q as quoted: n[ename] = a[theenv] assignment = quoted[0] tree.body.insert(0, assignment) elif type(tree) is Lambda and id(tree) in userlambdas: # We must in general inject a new do[] even if one is already there, # due to scoping rules. If the user code writes to the same names in # its do[] env, this shadows the formals; if it then pops one of its names, # the name should revert to mean the formal parameter. # # inject a do[] and reuse its env tree.body = _do(q[n["_here_"], a[tree.body]]) view = ExpandedDoView( tree.body) # view.body: [(lambda e14: ...), ...] ename = view.body[0].args.args[ 0].arg # do[] environment name theupdate = q[n[f"{ename}.update"]] thecall = q[a[theupdate]()] thecall.keywords = kws tree.body = splice_expression(thecall, tree.body, "_here_") newbindings.update( {k: q[n[f"{ename}.{k}"]] for k in argnames}) # "x" --> e.x self.generic_withstate(tree, enames=(enames + [ename]), bindings=newbindings) else: # leave alone the _envify() added by us if type(tree) is Call and (isx(tree.func, "_envify") or isourupdate(tree)): # don't recurse return tree # transform env-assignments into our envs elif isenvassign(tree): view = UnexpandedEnvAssignView(tree) if view.name in bindings.keys(): # Grab the envname from the actual binding of "varname", of the form `e.varname` # (so it's the `id` of a `Name` that is the `value` of an `Attribute`). envset = q[n[f"{bindings[view.name].value.id}.set"]] newvalue = self.visit(view.value) return q[a[envset](u[view.name], a[newvalue])] # transform references to currently active bindings # x --> e14.x # It doesn't matter if this hits an already expanded inner `with envify`, # because the gensymmed environment name won't be in our bindings, and the "x" # has become the `attr` in an `Attribute` node. elif type(tree) is Name and tree.id in bindings.keys(): # We must be careful to preserve the Load/Store/Del context of the name. # The default lets `mcpyrate` fix it later. ctx = tree.ctx if hasattr(tree, "ctx") else None out = deepcopy(bindings[tree.id]) out.ctx = ctx return out return self.generic_visit(tree)
def transform(self, tree): if is_captured_value(tree): return tree # don't recurse! # Not tuples but syntax: leave alone the: # - binding pair "tuples" of let, letseq, letrec, their d*, b* variants, # and let_syntax, abbrev # - subscript part of an explicit do[], do0[] # but recurse inside them. # # let and do have not expanded yet when prefix runs (better that way!). if islet(tree, expanded=False): view = UnexpandedLetView(tree) newbindings = [] for binding in view.bindings: if type(binding) is not Tuple: raise SyntaxError( "prefix: expected a tuple in let binding position" ) # pragma: no cover _, value = binding.elts # leave name alone, recurse into value binding.elts[1] = self.visit(value) newbindings.append(binding) view.bindings = newbindings # write the new bindings (important!) if view.body: view.body = self.visit(view.body) return tree elif isdo(tree, expanded=False): view = UnexpandedDoView(tree) view.body = self.visit(view.body) return tree # Integration with other macros, including the testing framework. # Macros may take a tuple as the top-level expr, but typically don't take slice syntax. # # Up to Python 3.8, a top-level tuple is packed into an Index: # ast.parse("a[1, 2]").body[0].value.slice # --> <_ast.Index at 0x7fd57505f208> # ast.parse("a[1, 2]").body[0].value.slice.value # --> <_ast.Tuple at 0x7fd590962ef0> # The structure is for this example is # Module # Expr # Subscript if type(tree) is Subscript: if sys.version_info >= ( 3, 9, 0): # Python 3.9+: the Index wrapper is gone. body = tree.slice else: body = tree.slice.value if type(body) is Tuple: # Skip the transformation of the expr tuple itself, but transform its elements. # This skips the transformation of the macro argument tuple, too, because # that's a nested Subscript (`(macro[a0, ...])[expr]`). body.elts = self.visit(body.elts) tree.value = self.visit(tree.value) return tree # in any other case, continue processing normally # general case # macro-created nodes might not have a ctx, but we run outside in. if not (type(tree) is Tuple and type(tree.ctx) is Load): return self.generic_visit(tree) op, *data = tree.elts quotelevel = self.state.quotelevel while True: if isunquote(op): if quotelevel < 1: raise SyntaxError( "unquote while not in quote") # pragma: no cover quotelevel -= 1 elif isquote(op): quotelevel += 1 else: break if not len(data): raise SyntaxError( "a prefix tuple cannot contain only quote/unquote operators" ) # pragma: no cover op, *data = data if quotelevel > 0: quoted = [op] + data if any(iskwargs(x) for x in quoted): raise SyntaxError( "kw(...) may only appear in a prefix tuple representing a function call" ) # pragma: no cover self.withstate(quoted, quotelevel=quotelevel) return q[t[self.visit(quoted)]] # (f, a1, ..., an) --> f(a1, ..., an) posargs = [x for x in data if not iskwargs(x)] kwargs_calls = [x for x in data if iskwargs(x)] # In Python 3.5+, this tags *args as invalid, too, because those are Starred items inside `args`. invalids = list( flatmap(lambda tree: tree.args, kwargs_calls)) # no positional args allowed in kw() kwargs = list(flatmap(lambda x: x.keywords, kwargs_calls)) invalids += [x for x in kwargs if type(x) is Starred] # reject **kwargs if invalids: raise SyntaxError( "kw(...) may only specify individual named args" ) # pragma: no cover kwargs = list(rev(uniqify(rev(kwargs), key=lambda x: x.arg)) ) # latest wins, but keep original ordering thecall = Call(func=op, args=posargs, keywords=list(kwargs)) self.withstate(thecall, quotelevel=quotelevel) return self.visit(thecall)
def transform(self, tree): if is_captured_value(tree): return tree # don't recurse! referents = self.state.referents if type(tree) in (Attribute, Subscript, Name) and type(tree.ctx) in (Store, Del): return tree # skip autoref lookup for let/do envs elif islet(tree): view = ExpandedLetView(tree) self.generic_withstate(tree, referents=referents + [view.body.args.args[0].arg]) # lambda e14: ... elif isdo(tree): view = ExpandedDoView(tree) self.generic_withstate(tree, referents=referents + [view.body[0].args.args[0].arg]) # lambda e14: ... elif isinstance(tree, ExpandedAutorefMarker): self.generic_withstate(tree, referents=referents + [tree.varname]) elif isautoreference(tree): # generated by an inner already expanded autoref block thename = getconstant(get_resolver_list(tree)[-1]) if thename in referents: # This case is tricky to trigger, so let's document it here. This code: # # with autoref[e]: # with autoref[e2]: # e # # expands to: # # $ASTMarker<ExpandedAutorefMarker>: # varname: '_o5' # body: # _o5 = e # $ASTMarker<ExpandedAutorefMarker>: # varname: '_o4' # body: # _o4 = (lambda _ar13: (_ar13[1] if _ar13[0] else e2))(_autoref_resolve((_o5, 'e2'))) # (lambda _ar9: (_ar9[1] if _ar9[0] else e))(_autoref_resolve((_o4, _o5, 'e'))) # # so there's no "e" as referent; the actual referent has a gensymmed name. # Inside the body of the inner autoref, looking up "e" in e2 before falling # back to the outer "e" is exactly what `autoref` is expected to do. # # Where is this used, then? The named variant `with autoref[...] as ...`: # # with step_expansion: # with autoref[e] as outer: # with autoref[e2] as inner: # outer # # expands to: # # $ASTMarker<ExpandedAutorefMarker>: # varname: 'outer' # body: # outer = e # $ASTMarker<ExpandedAutorefMarker>: # varname: 'inner' # body: # inner = (lambda _ar17: (_ar17[1] if _ar17[0] else e2))(_autoref_resolve((outer, 'e2'))) # outer # <-- !!! # # Now this case is triggered; we get a bare `outer` inside the inner body. # TODO: Whether this wart is a good idea is another question... # remove autoref lookup for an outer referent, inserted early by an inner autoref block # (that doesn't know that any outer block exists) tree = q[n[thename]] # (lambda ...)(_autoref_resolve((p, "o"))) --> o else: add_to_resolver_list(tree, q[n[o]]) # _autoref_resolve((p, "x")) --> _autoref_resolve((p, o, "x")) return tree elif isinstance(tree, ExpandedAutorefMarker): # nested autorefs return tree elif type(tree) is Name and (type(tree.ctx) is Load or not tree.ctx) and tree.id not in referents: tree = makeautoreference(tree) return tree # Attribute works as-is, because a.b.c --> Attribute(Attribute(a, "b"), "c"), so Name "a" gets transformed. # Subscript similarly, a[1][2] --> Subscript(Subscript(a, 1), 2), so Name "a" gets transformed. return self.generic_visit(tree)
def transform(self, tree): forcing_mode = self.state.forcing_mode # Forcing references (Name, Attribute, Subscript): # x -> f(x) # a.x -> f(force1(a).x) # a.b.x -> f(force1(force1(a).b).x) # a[j] -> f((force1(a))[force(j)]) # a[j][k] -> f(force1(force1(a)[force(j)])[force(k)]) # # where f is force, force1 or identity (optimized away) depending on # where the term appears; j and k may be indices or slices. # # Whenever not in Load context, f is identity. # # The idea is to apply just the right level of forcing to be able to # resolve the reference, and then decide what to do with the resolved # reference based on where it appears. # # For example, when subscripting a list, force1 it to unwrap it from # a promise if it happens to be inside one, but don't force its elements # just for the sake of resolving the reference. Then, apply f to the # whole subscript term (forcing the accessed slice of the list, if necessary). def f(tree): if type(tree.ctx) is Load: if forcing_mode == "full": return q[h[force](a[tree])] elif forcing_mode == "flat": return q[h[force1](a[tree])] # else forcing_mode == "off" return tree # Hygienic captures must be treated separately: if is_captured_value(tree): if forcing_mode in ("full", "flat"): return q[h[force](a[tree])] # else forcing_mode == "off" return tree elif type(tree) in (FunctionDef, AsyncFunctionDef, Lambda): if type(tree) is Lambda and id(tree) not in userlambdas: return self.generic_visit( tree ) # ignore macro-introduced lambdas (but recurse inside them) else: # mark this definition as lazy, and insert the interface wrapper # to allow also strict code to call this function if type(tree) is Lambda: lam = tree tree = q[h[passthrough_lazy_args](a[tree])] # TODO: This doesn't really do anything; we don't here see the chain # TODO: of Call nodes (decorators) that surround the Lambda node. tree = sort_lambda_decorators(tree) lam.body = self.visit(lam.body) else: k = suggest_decorator_index("passthrough_lazy_args", tree.decorator_list) # Force the decorators only after `suggest_decorator_index` # has suggested us where to put ours. # TODO: could make `suggest_decorator_index` ignore a `force()` wrapper. tree.decorator_list = self.visit(tree.decorator_list) if k is not None: tree.decorator_list.insert( k, q[h[passthrough_lazy_args]]) else: # passthrough_lazy_args should generally be as innermost as possible # (so that e.g. the curry decorator will see the function as lazy) tree.decorator_list.append( q[h[passthrough_lazy_args]]) tree.body = self.visit(tree.body) return tree elif type(tree) is Call: # We don't need to expand in the output of `_lazyrec`, # because we don't recurse further into the args of the call, # so the `lazify` transformer never sees the confusing `Subscript` # instances that are actually macro invocations for `lazy[]`. def transform_arg(tree): # add any needed force() invocations inside the tree, # but leave the top level of simple references untouched. isref = type(tree) in (Name, Attribute, Subscript) self.withstate(tree, forcing_mode=("off" if isref else "full")) tree = self.visit(tree) if not isref: # (re-)thunkify expr; a reference can be passed as-is. tree = _lazyrec(tree) return tree def transform_starred(tree, dstarred=False): isref = type(tree) in (Name, Attribute, Subscript) self.withstate(tree, forcing_mode=("off" if isref else "full")) tree = self.visit(tree) # lazify items if we have a literal container # we must avoid lazifying any other exprs, since a Lazy cannot be unpacked. if _is_literal_container(tree, maps_only=dstarred): tree = _lazyrec(tree) return tree # let bindings have a role similar to function arguments, so auto-lazify there # (LHSs are always new names, so no infinite loop trap for the unwary) if islet(tree): view = ExpandedLetView(tree) if view.mode == "let": for b in view.bindings.elts: # b = (name, value) b.elts[1] = transform_arg(b.elts[1]) else: # view.mode == "letrec": for b in view.bindings.elts: # b = (name, (lambda e: ...)) thelambda = b.elts[1] thelambda.body = transform_arg(thelambda.body) if view.body: # let decorators have no body inside the Call node thelambda = view.body thelambda.body = self.visit(thelambda.body) return tree # Don't lazify in calls to some specific functions we know to be strict. # Some of these are performance optimizations; others must be left as-is # for other macros to be able to see the original calls. (It also generates # cleaner expanded output.) # - `namelambda` (emitted by `let[]`, `do[]`, and `test[]`) # - All known container constructor calls (listed in `_ctorcalls_all`). # - `Lazy` takes a lambda, constructs a `Lazy` object; if we're calling `Lazy`, # the expression is already lazy. # - `_autoref_resolve` does the name lookup in `with autoref` blocks. # # Don't lazify in calls to return-value utilities, because return values # are never implicitly lazy in `unpythonic`. # - `Values` constructs a multiple-return-values and/or named return values. # - `(chain_conts(cc1, cc2))(args)` handles a return value in `with continuations`. elif (isdo(tree) or is_decorator(tree.func, "namelambda") or any(isx(tree.func, s) for s in _ctorcalls_all) or isx(tree.func, _expanded_lazy_name) or isx(tree.func, "_autoref_resolve") or isx(tree.func, "Values") or (type(tree.func) is Call and isx(tree.func.func, "chain_conts"))): # Here we know the operator (.func) to be one of specific names; # don't transform it to avoid confusing `lazyrec[]`. # # This is especially important, if this is an inner call in the # arglist of an outer, lazy call, since it must see any container # constructor calls that appear in the args. # # But *do* transform in the positional and named args of the call; # doing so generates the code to force any promises that are passed # to the function being called. # # TODO: correct forcing mode for recursion? We shouldn't need to forcibly use "full", # since maybe_force_args() already fully forces any remaining promises # in the args when calling a strict function. # NOTE v0.15.0: In practice, using whatever is the currently active mode seems to be fine. tree.args = self.visit(tree.args) tree.keywords = self.visit(tree.keywords) return tree else: # general case thefunc = self.visit(tree.func) # Lazify the arguments of the call. adata = [] for x in tree.args: if type(x) is Starred: # *args in Python 3.5+ v = transform_starred(x.value) v = Starred(value=q[a[v]]) else: v = transform_arg(x) adata.append(v) kwdata = [] for x in tree.keywords: if x.arg is None: # **kwargs in Python 3.5+ v = transform_starred(x.value, dstarred=True) else: v = transform_arg(x.value) kwdata.append((x.arg, v)) # Construct the call mycall = Call( func=q[h[maybe_force_args]], args=[q[a[thefunc]]] + [q[a[x]] for x in adata], keywords=[ keyword(arg=k, value=q[a[x]]) for k, x in kwdata ]) tree = mycall return tree # NOTE: We must expand all inner macro invocations before we hit this, or we'll produce nonsense. # Hence it is easiest to have `lazify` expand inside-out. elif type( tree) is Subscript: # force only accessed part of obj[...] # force the slice expression; it is needed to extract the relevant items. self.withstate(tree.slice, forcing_mode="full") tree.slice = self.visit(tree.slice) # resolve reference to the actual container without forcing its items. self.withstate(tree.value, forcing_mode="flat") tree.value = self.visit(tree.value) # using the currently active forcing mode, force the value returned # by the subscript expression. tree = f(tree) return tree elif type(tree) is Attribute: # a.b.c --> f(force1(force1(a).b).c) (Load) # --> force1(force1(a).b).c (Store) # attr="c", value=a.b # attr="b", value=a # Note in case of assignment to a compound, only the outermost # Attribute is in Store context. # # Recurse in flat mode. Consider lst = [[1, 2], 3] # lst[0] --> f(force1(lst)[0]), but # lst[0].append --> force1(force1(force1(lst)[0]).append) # Hence, looking up an attribute should only force **the object** # so that we can perform the attribute lookup on it, whereas # looking up values should finally f() the whole slice. # (In the above examples, we have omitted f() when it is identity; # in reality there is always an f() around the whole expr.) self.withstate(tree.value, forcing_mode="flat") tree.value = self.visit(tree.value) # using the currently active forcing mode, force the value returned # by the attribute expression. tree = f(tree) return tree elif type(tree) is Name and type(tree.ctx) is Load: # using the currently active forcing mode, force the value. tree = f(tree) # must not recurse when a Name changes into a Call. return tree return self.generic_visit(tree)
def transform(self, tree): if is_captured_value(tree): return tree # don't recurse! if islet(tree, expanded=False): # let bindings view = UnexpandedLetView(tree) newbindings = [] for b in view.bindings: b.elts[1], thelambda, match = nameit( getname(b.elts[0]), b.elts[1]) if match: thelambda.body = self.visit(thelambda.body) else: b.elts[1] = self.visit(b.elts[1]) newbindings.append(b) view.bindings = newbindings # write the new bindings (important!) view.body = self.visit(view.body) return tree # assumption: no one left-shifts by a literal lambda :) elif isenvassign(tree): # f << (lambda ...: ...) view = UnexpandedEnvAssignView(tree) view.value, thelambda, match = nameit(view.name, view.value) if match: thelambda.body = self.visit(thelambda.body) else: view.value = self.visit(view.value) return tree elif issingleassign(tree): # f = lambda ...: ... tree.value, thelambda, match = nameit(getname(tree.targets[0]), tree.value) if match: thelambda.body = self.visit(thelambda.body) else: tree.value = self.visit(tree.value) return tree elif type( tree ) is NamedExpr: # f := lambda ...: ... (Python 3.8+, added in unpythonic 0.15.0) tree.value, thelambda, match = nameit(getname(tree.target), tree.value) if match: thelambda.body = self.visit(thelambda.body) else: tree.value = self.visit(tree.value) return tree elif iscallwithnamedargs(tree): # foo(f=lambda: ...) for kw in tree.keywords: if kw.arg is None: # **kwargs in Python 3.5+ kw.value = self.visit(kw.value) continue # a single named arg kw.value, thelambda, match = nameit(kw.arg, kw.value) if match: thelambda.body = self.visit(thelambda.body) else: kw.value = self.visit(kw.value) tree.args = self.visit(tree.args) return tree elif type(tree) is Dict: # {"f": lambda: ..., "g": lambda: ...} lst = list(zip(tree.keys, tree.values)) for j in range(len(lst)): k, v = tree.keys[j], tree.values[j] if k is None: # {..., **d, ...} tree.values[j] = self.visit(v) else: if type(k) in (Constant, Str): # Python 3.8+: ast.Constant thename = getconstant(k) tree.values[j], thelambda, match = nameit( thename, v) if match: thelambda.body = self.visit(thelambda.body) else: tree.values[j] = self.visit(v) else: tree.keys[j] = self.visit(k) tree.values[j] = self.visit(v) return tree return self.generic_visit(tree)