google / pasta

Library to refactor python code through AST manipulation.
Apache License 2.0
341 stars 40 forks source link

Parsing for non-defaulted kwargs after `*args` fails #60

Closed athre0z closed 5 years ago

athre0z commented 5 years ago
import pasta

aaa = '''
def meow(self, *args, blah):
    pass
'''

pasta.parse(aaa)
---------------------------------------------------------------------------
AnnotationError                           Traceback (most recent call last)
<ipython-input-12-cf34fed7cd55> in <module>
      6 '''
      7
----> 8 pasta.parse(aaa)

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/__init__.py in parse(src)
     23   t = ast_utils.parse(src)
     24   annotator = annotate.AstAnnotator(src)
---> 25   annotator.visit(t)
     26   return t
     27

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/base/annotate.py in visit(self, node)
   1170       fmt.set(node, 'indent', self._indent)
   1171       fmt.set(node, 'indent_diff', self._indent_diff)
-> 1172       super(AstAnnotator, self).visit(node)
   1173     except (TypeError, ValueError, IndexError, KeyError) as e:
   1174       raise AnnotationError(e)

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/base/annotate.py in visit(self, node)
    130   def visit(self, node):
    131     self._stack.append(node)
--> 132     super(BaseVisitor, self).visit(node)
    133     assert node is self._stack.pop()
    134

/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ast.py in visit(self, node)
    260         method = 'visit_' + node.__class__.__name__
    261         visitor = getattr(self, method, self.generic_visit)
--> 262         return visitor(node)
    263
    264     def generic_visit(self, node):

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/base/annotate.py in wrapped(self, node, *args, **kwargs)
     45       if prefix:
     46         self.prefix(node, default=self._indent if statement else '')
---> 47       f(self, node, *args, **kwargs)
     48       if suffix:
     49         self.suffix(node, max_lines=max_suffix_lines, semicolon=semicolon,

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/base/annotate.py in visit_Module(self, node)
    218   @module
    219   def visit_Module(self, node):
--> 220     self.generic_visit(node)
    221
    222   @block_statement

/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ast.py in generic_visit(self, node)
    268                 for item in value:
    269                     if isinstance(item, AST):
--> 270                         self.visit(item)
    271             elif isinstance(value, AST):
    272                 self.visit(value)

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/base/annotate.py in visit(self, node)
   1170       fmt.set(node, 'indent', self._indent)
   1171       fmt.set(node, 'indent_diff', self._indent_diff)
-> 1172       super(AstAnnotator, self).visit(node)
   1173     except (TypeError, ValueError, IndexError, KeyError) as e:
   1174       raise AnnotationError(e)

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/base/annotate.py in visit(self, node)
    130   def visit(self, node):
    131     self._stack.append(node)
--> 132     super(BaseVisitor, self).visit(node)
    133     assert node is self._stack.pop()
    134

/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ast.py in visit(self, node)
    260         method = 'visit_' + node.__class__.__name__
    261         visitor = getattr(self, method, self.generic_visit)
--> 262         return visitor(node)
    263
    264     def generic_visit(self, node):

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/base/annotate.py in wrapped(self, node, *args, **kwargs)
     93   def wrapped(self, node, *args, **kwargs):
     94     self.prefix(node, default=self._indent)
---> 95     f(self, node, *args, **kwargs)
     96     if hasattr(self, 'block_suffix'):
     97       last_child = ast_utils.get_last_child(node)

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/base/annotate.py in visit_FunctionDef(self, node)
    399     with self.scope(node, 'args', trailing_comma=args_count > 0,
    400                     default_parens=True):
--> 401       self.visit(node.args)
    402
    403     if getattr(node, 'returns', None):

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/base/annotate.py in visit(self, node)
   1170       fmt.set(node, 'indent', self._indent)
   1171       fmt.set(node, 'indent_diff', self._indent_diff)
-> 1172       super(AstAnnotator, self).visit(node)
   1173     except (TypeError, ValueError, IndexError, KeyError) as e:
   1174       raise AnnotationError(e)

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/base/annotate.py in visit(self, node)
    130   def visit(self, node):
    131     self._stack.append(node)
--> 132     super(BaseVisitor, self).visit(node)
    133     assert node is self._stack.pop()
    134

/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ast.py in visit(self, node)
    260         method = 'visit_' + node.__class__.__name__
    261         visitor = getattr(self, method, self.generic_visit)
--> 262         return visitor(node)
    263
    264     def generic_visit(self, node):

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/base/annotate.py in wrapped(self, node, *args, **kwargs)
     45       if prefix:
     46         self.prefix(node, default=self._indent if statement else '')
---> 47       f(self, node, *args, **kwargs)
     48       if suffix:
     49         self.suffix(node, max_lines=max_suffix_lines, semicolon=semicolon,

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/base/annotate.py in visit_arguments(self, node)
   1083       self.visit(arg)
   1084       self.attr(node, 'kw_default_%d' % i, [self.ws, '=', self.ws],
-> 1085                 default='=')
   1086       self.visit(default)
   1087       arg_i += 1

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/base/annotate.py in attr(***failed resolving arguments***)
   1387     for attr_val in attr_vals:
   1388       if isinstance(attr_val, six.string_types):
-> 1389         attr_parts.append(self.token(attr_val))
   1390       else:
   1391         attr_parts.append(attr_val())

/private/tmp/test2/venv/lib/python3.7/site-packages/pasta/base/annotate.py in token(self, token_val)
   1313     if token.src != token_val:
   1314       raise AnnotationError("Expected %r but found %r\nline %d: %s" % (
-> 1315           token_val, token.src, token.start[0], token.line))
   1316
   1317     # If the token opens or closes a parentheses scope, keep track of it

AnnotationError: Expected '=' but found ')'
line 2: def meow(self, *args, blah):

This is valid syntax in Python 3. IIRC, this wasn't always the case but later added in a 3.X version, although I'm not exactly sure when.

soupytwist commented 5 years ago

Indeed this fails on at least 3.4+. Thanks for reporting!

athre0z commented 5 years ago

Reported and fixed within 10 minutes. That's one hell of a turn-around time! Thanks!

soupytwist commented 5 years ago

Your detailed bug report made it very easy :)

Thank you!