summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2019-11-20 06:58:20 -0800
committerGitHub <noreply@github.com>2019-11-20 06:58:20 -0800
commit5d33f673ce261220b0a39b1c7a07e0f3bc6f8c64 (patch)
tree84e6654a720f17b85458120c08e457b4e1863281
parentc206cc987eb6521e718b84b18ffa8f9dab8266ae (diff)
parent1f37d5f0d44b62852c2a564f30c0415da412639c (diff)
downloadjinja2-5d33f673ce261220b0a39b1c7a07e0f3bc6f8c64.tar.gz
Merge pull request #1105 from areebbeigh/issue-1102
nodes.py: Return on first false in Compare() expr
-rw-r--r--CHANGES.rst4
-rw-r--r--jinja2/nodes.py6
-rw-r--r--tests/test_lexnparse.py39
3 files changed, 41 insertions, 8 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 31aa480..f572e9a 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -77,6 +77,10 @@ Unreleased
:issue:`550`
- Use :func:`callable` to inject context at runtime for compatibility
with Cython compiled functions. :pr:`1108`
+- When chained comparisons of constants are evaluated at compile time,
+ the result follows Python's behavior of returning ``False`` if any
+ comparison returns ``False``, rather than only the last one.
+ :issue:`1102`
Version 2.10.3
diff --git a/jinja2/nodes.py b/jinja2/nodes.py
index 058e3e6..e46aa91 100644
--- a/jinja2/nodes.py
+++ b/jinja2/nodes.py
@@ -768,13 +768,19 @@ class Compare(Expr):
def as_const(self, eval_ctx=None):
eval_ctx = get_eval_context(self, eval_ctx)
result = value = self.expr.as_const(eval_ctx)
+
try:
for op in self.ops:
new_value = op.expr.as_const(eval_ctx)
result = _cmpop_to_func[op.op](value, new_value)
+
+ if not result:
+ return False
+
value = new_value
except Exception:
raise Impossible()
+
return result
diff --git a/tests/test_lexnparse.py b/tests/test_lexnparse.py
index cc941eb..b542282 100644
--- a/tests/test_lexnparse.py
+++ b/tests/test_lexnparse.py
@@ -323,16 +323,39 @@ class TestSyntax(object):
tmpl = env.from_string("{{ [1, 2] ~ 'foo' }}")
assert tmpl.render() == '[1, 2]foo'
- def test_compare(self, env):
- tmpl = env.from_string('{{ 1 > 0 }}|{{ 1 >= 1 }}|{{ 2 < 3 }}|'
- '{{ 2 == 2 }}|{{ 1 <= 1 }}')
- assert tmpl.render() == 'True|True|True|True|True'
+ @pytest.mark.parametrize(
+ ("a", "op", "b"),
+ [
+ (1, ">", 0),
+ (1, ">=", 1),
+ (2, "<", 3),
+ (3, "<=", 4),
+ (4, "==", 4),
+ (4, "!=", 5),
+ ],
+ )
+ def test_compare(self, env, a, op, b):
+ t = env.from_string("{{ %d %s %d }}" % (a, op, b))
+ assert t.render() == "True"
def test_compare_parens(self, env):
- tmpl = env.from_string(
- "{{ i*(j<5) }}"
- )
- assert tmpl.render(i=2, j=3) == '2'
+ t = env.from_string("{{ i * (j < 5) }}")
+ assert t.render(i=2, j=3) == "2"
+
+ @pytest.mark.parametrize(
+ ("src", "expect"),
+ [
+ ("{{ 4 < 2 < 3 }}", "False"),
+ ("{{ a < b < c }}", "False"),
+ ("{{ 4 > 2 > 3 }}", "False"),
+ ("{{ a > b > c }}", "False"),
+ ("{{ 4 > 2 < 3 }}", "True"),
+ ("{{ a > b < c }}", "True"),
+ ],
+ )
+ def test_compare_compound(self, env, src, expect):
+ t = env.from_string(src)
+ assert t.render(a=4, b=2, c=3) == expect
def test_inop(self, env):
tmpl = env.from_string('{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}')