Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bug in assignments #68

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion beniget/beniget.py
Original file line number Diff line number Diff line change
Expand Up @@ -1201,7 +1201,7 @@ def visit_Destructured(self, node):
tmp_store, elt.ctx = elt.ctx, tmp_store
self.visit(elt)
tmp_store, elt.ctx = elt.ctx, tmp_store
elif isinstance(elt, (ast.Subscript, ast.Starred)):
elif isinstance(elt, (ast.Subscript, ast.Starred, ast.Attribute)):
self.visit(elt)
elif isinstance(elt, (ast.List, ast.Tuple)):
self.visit_Destructured(elt)
Expand Down
39 changes: 38 additions & 1 deletion tests/test_chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,44 @@ class Attr(Attr):pass
['Attr -> (Attr -> (Attr -> ()))',
'Visitor -> ()'])
self.assertEqual(c.dump_chains(node.body[-1]), ['Attr -> ()'])


@skipIf(sys.version_info < (3, 0), 'Python 3 syntax')
tristanlatr marked this conversation as resolved.
Show resolved Hide resolved
def test_star_assignment(self):
code = '''
curr, *parts = [1,2,3]
while curr:
print(curr)
if parts:
curr, *parts = parts
else:
break
'''
self.checkChains(code, ['curr -> (curr -> (), curr -> (Call -> ()))',
'parts -> (parts -> (), parts -> ())']*2)

@skipIf(sys.version_info < (3, 0), 'Python 3 syntax')
def test_star_assignment_nested(self):
code = '''
(curr, *parts),i = [1,2,3],0
while curr:
print(curr)
if parts:
(curr, *parts),i = parts,i
else:
break
'''
self.checkChains(code, ['curr -> (curr -> (), curr -> (Call -> ()))',
'parts -> (parts -> (), parts -> (Tuple -> ()))',
'i -> (i -> (Tuple -> ()))']*2)

def test_attribute_assignment(self):
tristanlatr marked this conversation as resolved.
Show resolved Hide resolved
code = "d=object();d.name,x = 't',1"
self.checkChains(code, ['d -> (d -> (Attribute -> ()))',
'x -> ()'])

def test_call_assignment(self):
code = "NameError().name = 't'"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the ast.Call instance will never be analyzed because it's not part of the supported nodes for assignments.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's not bound to any identifier... We could create temporary identifier for unbound expression that get assigned, but let's keep that for another patch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it won't be visited at all, so the ast.Call instance uses won't be part of the chains at all, even if we could argue that it's still a user of NameError, the same manner NameError won't be added to the uses of the builtins NameError symbol. This is why I think we should unconditionally visit this part.

self.checkChains(code, [])

@skipIf(sys.version_info < (3, 0), 'Python 3 syntax')
def test_annotation_uses_class_level_name(self):
Expand Down