Skip to content

Commit b8f4831

Browse files
committed
more comments about nsref assignment
only emit nsref instance check once per ref name refactor primary name parsing a bit
1 parent ee83219 commit b8f4831

File tree

2 files changed

+27
-15
lines changed

2 files changed

+27
-15
lines changed

src/jinja2/compiler.py

+16-8
Original file line numberDiff line numberDiff line change
@@ -1582,12 +1582,19 @@ def visit_Output(self, node: nodes.Output, frame: Frame) -> None:
15821582
def visit_Assign(self, node: nodes.Assign, frame: Frame) -> None:
15831583
self.push_assign_tracking()
15841584

1585-
# NSRef can only ever be used during assignment so we need to check
1586-
# to make sure that it is only being used to assign using a Namespace.
1587-
# This check is done here because it is used an expression during the
1588-
# assignment and therefore cannot have this check done when the NSRef
1589-
# node is visited
1585+
# ``a.b`` is allowed for assignment, and is parsed as an NSRef. However,
1586+
# it is only valid if it references a Namespace object. Emit a check for
1587+
# that for each ref here, before assignment code is emitted. This can't
1588+
# be done in visit_NSRef as the ref could be in the middle of a tuple.
1589+
seen_refs: t.Set[str] = set()
1590+
15901591
for nsref in node.find_all(nodes.NSRef):
1592+
if nsref.name in seen_refs:
1593+
# Only emit the check for each reference once, in case the same
1594+
# ref is used multiple times in a tuple, `ns.a, ns.b = c, d`.
1595+
continue
1596+
1597+
seen_refs.add(nsref.name)
15911598
ref = frame.symbols.ref(nsref.name)
15921599
self.writeline(f"if not isinstance({ref}, Namespace):")
15931600
self.indent()
@@ -1653,9 +1660,10 @@ def visit_Name(self, node: nodes.Name, frame: Frame) -> None:
16531660
self.write(ref)
16541661

16551662
def visit_NSRef(self, node: nodes.NSRef, frame: Frame) -> None:
1656-
# NSRefs can only be used to store values; since they use the normal
1657-
# `foo.bar` notation they will be parsed as a normal attribute access
1658-
# when used anywhere but in a `set` context
1663+
# NSRef is a dotted assignment target a.b=c, but uses a[b]=c internally.
1664+
# visit_Assign emits code to validate that each ref is to a Namespace
1665+
# object only. That can't be emitted here as the ref could be in the
1666+
# middle of a tuple assignment.
16591667
ref = frame.symbols.ref(node.name)
16601668
self.writeline(f"{ref}[{node.attr!r}]")
16611669

src/jinja2/parser.py

+11-7
Original file line numberDiff line numberDiff line change
@@ -641,21 +641,24 @@ def parse_unary(self, with_filter: bool = True) -> nodes.Expr:
641641
return node
642642

643643
def parse_primary(self, with_namespace: bool = False) -> nodes.Expr:
644+
"""Parse a name or literal value. If ``with_namespace`` is enabled, also
645+
parse namespace attr refs, for use in assignments."""
644646
token = self.stream.current
645647
node: nodes.Expr
646648
if token.type == "name":
649+
next(self.stream)
647650
if token.value in ("true", "false", "True", "False"):
648651
node = nodes.Const(token.value in ("true", "True"), lineno=token.lineno)
649652
elif token.value in ("none", "None"):
650653
node = nodes.Const(None, lineno=token.lineno)
651-
elif with_namespace and self.stream.look().type == "dot":
652-
next(self.stream) # token
653-
next(self.stream) # dot
654-
attr = self.stream.current
654+
elif with_namespace and self.stream.current.type == "dot":
655+
# If namespace attributes are allowed at this point, and the next
656+
# token is a dot, produce a namespace reference.
657+
next(self.stream)
658+
attr = self.stream.expect("name")
655659
node = nodes.NSRef(token.value, attr.value, lineno=token.lineno)
656660
else:
657661
node = nodes.Name(token.value, "load", lineno=token.lineno)
658-
next(self.stream)
659662
elif token.type == "string":
660663
next(self.stream)
661664
buf = [token.value]
@@ -693,8 +696,9 @@ def parse_tuple(
693696
if no commas where found.
694697
695698
The default parsing mode is a full tuple. If `simplified` is `True`
696-
only names and literals are parsed. The `no_condexpr` parameter is
697-
forwarded to :meth:`parse_expression`.
699+
only names and literals are parsed; ``with_namespace`` allows namespace
700+
attr refs as well. The `no_condexpr` parameter is forwarded to
701+
:meth:`parse_expression`.
698702
699703
Because tuples do not require delimiters and may end in a bogus comma
700704
an extra hint is needed that marks the end of a tuple. For example

0 commit comments

Comments
 (0)