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: t.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()
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}]")
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]
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