diff --git a/jinja2/nodes.py b/jinja2/nodes.py index d32046ce5..7287c0145 100644 --- a/jinja2/nodes.py +++ b/jinja2/nodes.py @@ -94,16 +94,6 @@ def revert(self, old): self.__dict__.update(old) -def get_eval_context(node, ctx): - if ctx is None: - if node.environment is None: - raise RuntimeError('if no eval context is passed, the ' - 'node must have an attached ' - 'environment.') - return EvalContext(node.environment) - return ctx - - class Node(with_metaclass(NodeType, object)): """Baseclass for all Jinja2 nodes. There are a number of nodes available of different types. There are four major types: @@ -356,14 +346,10 @@ class Expr(Node): """Baseclass for all expressions.""" abstract = True - def as_const(self, eval_ctx=None): + def as_const(self, eval_ctx): """Return the value of the expression as constant or raise :exc:`Impossible` if this was not possible. - An :class:`EvalContext` can be provided, if none is given - a default context is created which requires the nodes to have - an attached environment. - .. versionchanged:: 2.4 the `eval_ctx` parameter was added. """ @@ -380,8 +366,7 @@ class BinExpr(Expr): operator = None abstract = True - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): # intercepted operators cannot be folded at compile time if self.environment.sandboxed and \ self.operator in self.environment.intercepted_binops: @@ -399,8 +384,7 @@ class UnaryExpr(Expr): operator = None abstract = True - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): # intercepted operators cannot be folded at compile time if self.environment.sandboxed and \ self.operator in self.environment.intercepted_unops: @@ -440,7 +424,7 @@ class Const(Literal): """ fields = ('value',) - def as_const(self, eval_ctx=None): + def as_const(self, eval_ctx): return self.value @classmethod @@ -459,8 +443,7 @@ class TemplateData(Literal): """A constant template string.""" fields = ('data',) - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): if eval_ctx.volatile: raise Impossible() if eval_ctx.autoescape: @@ -475,8 +458,7 @@ class Tuple(Literal): """ fields = ('items', 'ctx') - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): return tuple(x.as_const(eval_ctx) for x in self.items) def can_assign(self): @@ -490,8 +472,7 @@ class List(Literal): """Any list literal such as ``[1, 2, 3]``""" fields = ('items',) - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): return [x.as_const(eval_ctx) for x in self.items] @@ -501,8 +482,7 @@ class Dict(Literal): """ fields = ('items',) - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): return dict(x.as_const(eval_ctx) for x in self.items) @@ -510,8 +490,7 @@ class Pair(Helper): """A key, value pair for dicts.""" fields = ('key', 'value') - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx) @@ -519,8 +498,7 @@ class Keyword(Helper): """A key, value pair for keyword arguments where key is a string.""" fields = ('key', 'value') - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): return self.key, self.value.as_const(eval_ctx) @@ -530,8 +508,7 @@ class CondExpr(Expr): """ fields = ('test', 'expr1', 'expr2') - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): if self.test.as_const(eval_ctx): return self.expr1.as_const(eval_ctx) @@ -551,9 +528,8 @@ class Filter(Expr): """ fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - if eval_ctx.volatile or self.node is None: + def as_const(self, eval_ctx): + if self.node is None: raise Impossible() # we have to be careful here because we call filter_ below. # if this variable would be called filter, 2to3 would wrap the @@ -563,9 +539,10 @@ def as_const(self, eval_ctx=None): filter_ = self.environment.filters.get(self.name) if filter_ is None or getattr(filter_, 'contextfilter', False): raise Impossible() - obj = self.node.as_const(eval_ctx) - args = [x.as_const(eval_ctx) for x in self.args] + args = [x.as_const(eval_ctx) for x in [self.node] + self.args] if getattr(filter_, 'evalcontextfilter', False): + if eval_ctx.volatile: + raise Impossible() args.insert(0, eval_ctx) elif getattr(filter_, 'environmentfilter', False): args.insert(0, self.environment) @@ -581,7 +558,7 @@ def as_const(self, eval_ctx=None): except Exception: raise Impossible() try: - return filter_(obj, *args, **kwargs) + return filter_(*args, **kwargs) except Exception: raise Impossible() @@ -602,10 +579,7 @@ class Call(Expr): """ fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - if eval_ctx.volatile: - raise Impossible() + def as_const(self, eval_ctx): obj = self.node.as_const(eval_ctx) # don't evaluate context functions @@ -614,6 +588,8 @@ def as_const(self, eval_ctx=None): if getattr(obj, 'contextfunction', False): raise Impossible() elif getattr(obj, 'evalcontextfunction', False): + if eval_ctx.volatile: + raise Impossible() args.insert(0, eval_ctx) elif getattr(obj, 'environmentfunction', False): args.insert(0, self.environment) @@ -639,8 +615,7 @@ class Getitem(Expr): """Get an attribute or item from an expression and prefer the item.""" fields = ('node', 'arg', 'ctx') - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): if self.ctx != 'load': raise Impossible() try: @@ -659,11 +634,10 @@ class Getattr(Expr): """ fields = ('node', 'attr', 'ctx') - def as_const(self, eval_ctx=None): + def as_const(self, eval_ctx): if self.ctx != 'load': raise Impossible() try: - eval_ctx = get_eval_context(self, eval_ctx) return self.environment.getattr(self.node.as_const(eval_ctx), self.attr) except Exception: @@ -679,8 +653,7 @@ class Slice(Expr): """ fields = ('start', 'stop', 'step') - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): def const(obj): if obj is None: return None @@ -694,8 +667,7 @@ class Concat(Expr): """ fields = ('nodes',) - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): return ''.join(text_type(x.as_const(eval_ctx)) for x in self.nodes) @@ -705,8 +677,7 @@ class Compare(Expr): """ fields = ('expr', 'ops') - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): result = value = self.expr.as_const(eval_ctx) try: for op in self.ops: @@ -769,8 +740,7 @@ class And(BinExpr): """Short circuited AND.""" operator = 'and' - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx) @@ -778,8 +748,7 @@ class Or(BinExpr): """Short circuited OR.""" operator = 'or' - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx) @@ -845,8 +814,7 @@ class MarkSafe(Expr): """Mark the wrapped expression as safe (wrap it as `Markup`).""" fields = ('expr',) - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): return Markup(self.expr.as_const(eval_ctx)) @@ -858,8 +826,7 @@ class MarkSafeIfAutoescape(Expr): """ fields = ('expr',) - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) + def as_const(self, eval_ctx): if eval_ctx.volatile: raise Impossible() expr = self.expr.as_const(eval_ctx) diff --git a/jinja2/optimizer.py b/jinja2/optimizer.py index 00eab115e..bf3a80d7c 100644 --- a/jinja2/optimizer.py +++ b/jinja2/optimizer.py @@ -31,6 +31,7 @@ class Optimizer(NodeTransformer): def __init__(self, environment): self.environment = environment + self.eval_ctx = nodes.EvalContext(environment) def visit_If(self, node): """Eliminate dead code.""" @@ -39,7 +40,7 @@ def visit_If(self, node): if node.find(nodes.Block) is not None: return self.generic_visit(node) try: - val = self.visit(node.test).as_const() + val = self.visit(node.test).as_const(self.eval_ctx) except nodes.Impossible: return self.generic_visit(node) if val: @@ -51,11 +52,27 @@ def visit_If(self, node): result.extend(self.visit_list(node)) return result + def visit_EvalContextModifier(self, node): + for keyword in node.options: + try: + value = keyword.value.as_const(self.eval_ctx) + except nodes.Impossible: + self.eval_ctx.volatile = True + else: + setattr(self.eval_ctx, keyword.key, value) + return self.generic_visit(node) + + def visit_ScopedEvalContextModifier(self, node): + backup = self.eval_ctx.save() + node = self.visit_EvalContextModifier(node) + self.eval_ctx.revert(backup) + return node + def fold(self, node): """Do constant folding.""" node = self.generic_visit(node) try: - return nodes.Const.from_untrusted(node.as_const(), + return nodes.Const.from_untrusted(node.as_const(self.eval_ctx), lineno=node.lineno, environment=self.environment) except nodes.Impossible: