Skip to content

Commit

Permalink
more comments about nsref assignment
Browse files Browse the repository at this point in the history
only emit nsref instance check once per ref name
refactor primary name parsing a bit
  • Loading branch information
davidism committed Dec 20, 2024
1 parent ee83219 commit 2c971d7
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 15 deletions.
24 changes: 16 additions & 8 deletions src/jinja2/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1582,12 +1582,19 @@ def visit_Output(self, node: nodes.Output, frame: Frame) -> None:
def visit_Assign(self, node: nodes.Assign, frame: Frame) -> None:
self.push_assign_tracking()

# NSRef can only ever be used during assignment so we need to check
# to make sure that it is only being used to assign using a Namespace.
# This check is done here because it is used an expression during the
# assignment and therefore cannot have this check done when the NSRef
# node is visited
# ``a.b`` is allowed for assignment, and is parsed as an NSRef. However,
# it is only valid if it references a Namespace object. Emit a check for
# that for each ref here, before assignment code is emitted. This can't
# be done in visit_NSRef as the ref could be in the middle of a tuple.
seen_refs: set[str] = set()

for nsref in node.find_all(nodes.NSRef):
if nsref.name in seen_refs:
# Only emit the check for each reference once, in case the same
# ref is used multiple times in a tuple, `ns.a, ns.b = c, d`.
continue

seen_refs.add(nsref.name)
ref = frame.symbols.ref(nsref.name)
self.writeline(f"if not isinstance({ref}, Namespace):")
self.indent()
Expand Down Expand Up @@ -1653,9 +1660,10 @@ def visit_Name(self, node: nodes.Name, frame: Frame) -> None:
self.write(ref)

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

Expand Down
18 changes: 11 additions & 7 deletions src/jinja2/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,21 +641,24 @@ def parse_unary(self, with_filter: bool = True) -> nodes.Expr:
return node

def parse_primary(self, with_namespace: bool = False) -> nodes.Expr:
"""Parse a name or literal value. If ``with_namespace`` is enabled, also
parse namespace attr refs, for use in assignments."""
token = self.stream.current
node: nodes.Expr
if token.type == "name":
next(self.stream)
if token.value in ("true", "false", "True", "False"):
node = nodes.Const(token.value in ("true", "True"), lineno=token.lineno)
elif token.value in ("none", "None"):
node = nodes.Const(None, lineno=token.lineno)
elif with_namespace and self.stream.look().type == "dot":
next(self.stream) # token
next(self.stream) # dot
attr = self.stream.current
elif with_namespace and self.stream.current.type == "dot":
# If namespace attributes are allowed at this point, and the next
# token is a dot, produce a namespace reference.
next(self.stream)
attr = self.stream.expect("name")
node = nodes.NSRef(token.value, attr.value, lineno=token.lineno)
else:
node = nodes.Name(token.value, "load", lineno=token.lineno)
next(self.stream)
elif token.type == "string":
next(self.stream)
buf = [token.value]
Expand Down Expand Up @@ -693,8 +696,9 @@ def parse_tuple(
if no commas where found.
The default parsing mode is a full tuple. If `simplified` is `True`
only names and literals are parsed. The `no_condexpr` parameter is
forwarded to :meth:`parse_expression`.
only names and literals are parsed; ``with_namespace`` allows namespace
attr refs as well. The `no_condexpr` parameter is forwarded to
:meth:`parse_expression`.
Because tuples do not require delimiters and may end in a bogus comma
an extra hint is needed that marks the end of a tuple. For example
Expand Down

0 comments on commit 2c971d7

Please sign in to comment.