# HG changeset patch # User Paul Boddie # Date 1485298838 -3600 # Node ID 12828fba50e0ab151c5bd96dfb36c0584711c78b # Parent 6d062b05c2501f7d2c966824f1680b63c0e9e13e Re-introduced direct assignment of names in sequence assignments provided that only plain names are assigned (not attributes) and that any names accessed in the assignments do not appear amongst the assigned names. Added nested sequence assignment tests. diff -r 6d062b05c250 -r 12828fba50e0 common.py --- a/common.py Tue Jan 24 19:10:31 2017 +0100 +++ b/common.py Wed Jan 25 00:00:38 2017 +0100 @@ -394,9 +394,18 @@ name_ref = self.process_structure_node(expr) - # Have the assignment nodes access each item via the sequence API. + # Either unpack the items and present them directly to each assignment + # node. + + if isinstance(name_ref, LiteralSequenceRef) and \ + self.process_literal_sequence_items(n, name_ref): - self.process_assignment_node_items_by_position(n, expr, name_ref) + pass + + # Or have the assignment nodes access each item via the sequence API. + + else: + self.process_assignment_node_items_by_position(n, expr, name_ref) def process_assignment_node_items_by_position(self, n, expr, name_ref): @@ -441,11 +450,31 @@ """ Process the given assignment node 'n', obtaining from the given 'name_ref' the items to be assigned to the assignment targets. + + Return whether this method was able to process the assignment node as + a sequence of direct assignments. """ if len(n.nodes) == len(name_ref.items): - for node, item in zip(n.nodes, name_ref.items): - self.process_assignment_node(node, item) + assigned_names, count = get_names_from_nodes(n.nodes) + accessed_names, _count = get_names_from_nodes(name_ref.items) + + # Only assign directly between items if all assigned names are + # plain names (not attribute assignments), and if the assigned names + # do not appear in the accessed names. + + if len(assigned_names) == count and \ + not assigned_names.intersection(accessed_names): + + for node, item in zip(n.nodes, name_ref.items): + self.process_assignment_node(node, item) + + return True + + # Otherwise, use the position-based mechanism to obtain values. + + else: + return False else: raise InspectError("In %s, item assignment needing %d items is given %d items." % ( self.get_namespace_path(), len(n.nodes), len(name_ref.items))) @@ -796,6 +825,40 @@ l.append(arg) return l +def get_names_from_nodes(nodes): + + """ + Return the names employed in the given 'nodes' along with the number of + nodes excluding sequences. + """ + + names = set() + count = 0 + + for node in nodes: + + # Add names and count them. + + if isinstance(node, (compiler.ast.AssName, compiler.ast.Name)): + names.add(node.name) + count += 1 + + # Add names from sequences and incorporate their counts. + + elif isinstance(node, (compiler.ast.AssList, compiler.ast.AssTuple, + compiler.ast.List, compiler.ast.Set, + compiler.ast.Tuple)): + _names, _count = get_names_from_nodes(node.nodes) + names.update(_names) + count += _count + + # Count non-name, non-sequence nodes. + + else: + count += 1 + + return names, count + # Result classes. class InstructionSequence: diff -r 6d062b05c250 -r 12828fba50e0 tests/swap.py --- a/tests/swap.py Tue Jan 24 19:10:31 2017 +0100 +++ b/tests/swap.py Wed Jan 25 00:00:38 2017 +0100 @@ -1,10 +1,41 @@ +# Test attribute accesses with sequence assignments. + class C: a = 1; b = 2; c = 3 +# This cannot assign directly... + print C.a, C.b, C.c # 1 2 3 C.a, C.b, C.c = C.c, C.b, C.a print C.a, C.b, C.c # 3 2 1 +# This cannot assign directly... + D = C C.a, C.b, C.c = D.c, D.b, D.a print C.a, C.b, C.c # 1 2 3 + +# Test name accesses with sequence assignments. + +a = 1; b = 2; c = 3 + +# This cannot assign directly... + +print a, b, c # 1 2 3 +a, b, c = c, b, a +print a, b, c # 3 2 1 + +# This can assign directly... + +d, e, f = c, b, a +print d, e, f # 1 2 3 + +# This can assign directly... + +a, (b, c) = d, (e, f) +print a, b, c # 1 2 3 + +# This cannot assign directly... + +(c, b), a = (a, b), c +print a, b, c # 3 2 1