Commit b6b5152f authored by Stefan Behnel's avatar Stefan Behnel

properly handle expressions at the beginning of func/class/etc. blocks that...

properly handle expressions at the beginning of func/class/etc. blocks that start with a string literal but are not docstrings
parent fc0a514a
...@@ -121,13 +121,20 @@ class NormalizeTree(CythonTransform): ...@@ -121,13 +121,20 @@ class NormalizeTree(CythonTransform):
def visit_CStructOrUnionDefNode(self, node): def visit_CStructOrUnionDefNode(self, node):
return self.visit_StatNode(node, True) return self.visit_StatNode(node, True)
# Eliminate PassStatNode
def visit_PassStatNode(self, node): def visit_PassStatNode(self, node):
"""Eliminate PassStatNode"""
if not self.is_in_statlist: if not self.is_in_statlist:
return Nodes.StatListNode(pos=node.pos, stats=[]) return Nodes.StatListNode(pos=node.pos, stats=[])
else: else:
return [] return []
def visit_ExprStatNode(self, node):
"""Eliminate useless string literals"""
if node.expr.is_string_literal:
return None
self.visitchildren(node)
return node
def visit_CDeclaratorNode(self, node): def visit_CDeclaratorNode(self, node):
return node return node
......
...@@ -125,7 +125,9 @@ cdef p_DEF_statement(PyrexScanner s) ...@@ -125,7 +125,9 @@ cdef p_DEF_statement(PyrexScanner s)
cdef p_IF_statement(PyrexScanner s, ctx) cdef p_IF_statement(PyrexScanner s, ctx)
cdef p_statement(PyrexScanner s, ctx, bint first_statement = *) cdef p_statement(PyrexScanner s, ctx, bint first_statement = *)
cdef p_statement_list(PyrexScanner s, ctx, bint first_statement = *) cdef p_statement_list(PyrexScanner s, ctx, bint first_statement = *)
cdef p_suite(PyrexScanner s, ctx = *, bint with_doc = *, bint with_pseudo_doc = *) cdef p_suite(PyrexScanner s, ctx = *)
cdef tuple p_suite_with_docstring(s, ctx, with_doc_only = *)
cdef tuple _extract_docstring(node)
cdef p_positional_and_keyword_args(PyrexScanner s, end_sy_set, templates = *) cdef p_positional_and_keyword_args(PyrexScanner s, end_sy_set, templates = *)
cpdef p_c_base_type(PyrexScanner s, bint self_flag = *, bint nonempty = *, templates = *) cpdef p_c_base_type(PyrexScanner s, bint self_flag = *, bint nonempty = *, templates = *)
......
...@@ -1141,10 +1141,7 @@ def p_expression_or_assignment(s): ...@@ -1141,10 +1141,7 @@ def p_expression_or_assignment(s):
rhs = p_testlist(s) rhs = p_testlist(s)
return Nodes.InPlaceAssignmentNode(lhs.pos, operator = operator, lhs = lhs, rhs = rhs) return Nodes.InPlaceAssignmentNode(lhs.pos, operator = operator, lhs = lhs, rhs = rhs)
expr = expr_list[0] expr = expr_list[0]
if isinstance(expr, (ExprNodes.UnicodeNode, ExprNodes.StringNode, ExprNodes.BytesNode)): return Nodes.ExprStatNode(expr.pos, expr=expr)
return Nodes.PassStatNode(expr.pos)
else:
return Nodes.ExprStatNode(expr.pos, expr = expr)
rhs = expr_list[-1] rhs = expr_list[-1]
if len(expr_list) == 2: if len(expr_list) == 2:
...@@ -1888,7 +1885,7 @@ def p_statement(s, ctx, first_statement = 0): ...@@ -1888,7 +1885,7 @@ def p_statement(s, ctx, first_statement = 0):
elif ctx.level == 'c_class' and s.sy == 'IDENT' and s.systring == 'property': elif ctx.level == 'c_class' and s.sy == 'IDENT' and s.systring == 'property':
return p_property_decl(s) return p_property_decl(s)
elif s.sy == 'pass' and ctx.level != 'property': elif s.sy == 'pass' and ctx.level != 'property':
return p_pass_statement(s, with_newline = 1) return p_pass_statement(s, with_newline=True)
else: else:
if ctx.level in ('c_class_pxd', 'property'): if ctx.level in ('c_class_pxd', 'property'):
s.error("Executable statement not allowed here") s.error("Executable statement not allowed here")
...@@ -1923,15 +1920,18 @@ def p_statement_list(s, ctx, first_statement = 0): ...@@ -1923,15 +1920,18 @@ def p_statement_list(s, ctx, first_statement = 0):
else: else:
return Nodes.StatListNode(pos, stats = stats) return Nodes.StatListNode(pos, stats = stats)
def p_suite(s, ctx = Ctx(), with_doc = 0, with_pseudo_doc = 0):
pos = s.position() def p_suite(s, ctx=Ctx()):
return p_suite_with_docstring(s, ctx, with_doc_only=False)[1]
def p_suite_with_docstring(s, ctx, with_doc_only=False):
s.expect(':') s.expect(':')
doc = None doc = None
stmts = []
if s.sy == 'NEWLINE': if s.sy == 'NEWLINE':
s.next() s.next()
s.expect_indent() s.expect_indent()
if with_doc or with_pseudo_doc: if with_doc_only:
doc = p_doc_string(s) doc = p_doc_string(s)
body = p_statement_list(s, ctx) body = p_statement_list(s, ctx)
s.expect_dedent() s.expect_dedent()
...@@ -1943,10 +1943,10 @@ def p_suite(s, ctx = Ctx(), with_doc = 0, with_pseudo_doc = 0): ...@@ -1943,10 +1943,10 @@ def p_suite(s, ctx = Ctx(), with_doc = 0, with_pseudo_doc = 0):
else: else:
body = p_pass_statement(s) body = p_pass_statement(s)
s.expect_newline("Syntax error in declarations") s.expect_newline("Syntax error in declarations")
if with_doc: if not with_doc_only:
return doc, body doc, body = _extract_docstring(body)
else: return doc, body
return body
def p_positional_and_keyword_args(s, end_sy_set, templates = None): def p_positional_and_keyword_args(s, end_sy_set, templates = None):
""" """
...@@ -2797,7 +2797,7 @@ def p_c_func_or_var_declaration(s, pos, ctx): ...@@ -2797,7 +2797,7 @@ def p_c_func_or_var_declaration(s, pos, ctx):
if s.sy == ':': if s.sy == ':':
if ctx.level not in ('module', 'c_class', 'module_pxd', 'c_class_pxd', 'cpp_class') and not ctx.templates: if ctx.level not in ('module', 'c_class', 'module_pxd', 'c_class_pxd', 'cpp_class') and not ctx.templates:
s.error("C function definition not allowed here") s.error("C function definition not allowed here")
doc, suite = p_suite(s, Ctx(level = 'function'), with_doc = 1) doc, suite = p_suite_with_docstring(s, Ctx(level='function'))
result = Nodes.CFuncDefNode(pos, result = Nodes.CFuncDefNode(pos,
visibility = ctx.visibility, visibility = ctx.visibility,
base_type = base_type, base_type = base_type,
...@@ -2884,7 +2884,7 @@ def p_def_statement(s, decorators=None): ...@@ -2884,7 +2884,7 @@ def p_def_statement(s, decorators=None):
pos = s.position() pos = s.position()
s.next() s.next()
name = EncodedString( p_ident(s) ) name = EncodedString( p_ident(s) )
s.expect('('); s.expect('(')
args, star_arg, starstar_arg = p_varargslist(s, terminator=')') args, star_arg, starstar_arg = p_varargslist(s, terminator=')')
s.expect(')') s.expect(')')
if p_nogil(s): if p_nogil(s):
...@@ -2893,7 +2893,7 @@ def p_def_statement(s, decorators=None): ...@@ -2893,7 +2893,7 @@ def p_def_statement(s, decorators=None):
if s.sy == '->': if s.sy == '->':
s.next() s.next()
return_type_annotation = p_test(s) return_type_annotation = p_test(s)
doc, body = p_suite(s, Ctx(level = 'function'), with_doc = 1) doc, body = p_suite_with_docstring(s, Ctx(level='function'))
return Nodes.DefNode(pos, name = name, args = args, return Nodes.DefNode(pos, name = name, args = args,
star_arg = star_arg, starstar_arg = starstar_arg, star_arg = star_arg, starstar_arg = starstar_arg,
doc = doc, body = body, decorators = decorators, doc = doc, body = body, decorators = decorators,
...@@ -2944,8 +2944,8 @@ def p_class_statement(s, decorators): ...@@ -2944,8 +2944,8 @@ def p_class_statement(s, decorators):
pos, positional_args, keyword_args, star_arg, None) pos, positional_args, keyword_args, star_arg, None)
if arg_tuple is None: if arg_tuple is None:
# XXX: empty arg_tuple # XXX: empty arg_tuple
arg_tuple = ExprNodes.TupleNode(pos, args = []) arg_tuple = ExprNodes.TupleNode(pos, args=[])
doc, body = p_suite(s, Ctx(level = 'class'), with_doc = 1) doc, body = p_suite_with_docstring(s, Ctx(level='class'))
return Nodes.PyClassDefNode(pos, return Nodes.PyClassDefNode(pos,
name = class_name, name = class_name,
bases = arg_tuple, bases = arg_tuple,
...@@ -2993,7 +2993,7 @@ def p_c_class_definition(s, pos, ctx): ...@@ -2993,7 +2993,7 @@ def p_c_class_definition(s, pos, ctx):
body_level = 'c_class_pxd' body_level = 'c_class_pxd'
else: else:
body_level = 'c_class' body_level = 'c_class'
doc, body = p_suite(s, Ctx(level = body_level), with_doc = 1) doc, body = p_suite_with_docstring(s, Ctx(level=body_level))
else: else:
s.expect_newline("Syntax error in C class definition") s.expect_newline("Syntax error in C class definition")
doc = None doc = None
...@@ -3050,12 +3050,15 @@ def p_c_class_options(s): ...@@ -3050,12 +3050,15 @@ def p_c_class_options(s):
s.expect(']', "Expected 'object' or 'type'") s.expect(']', "Expected 'object' or 'type'")
return objstruct_name, typeobj_name return objstruct_name, typeobj_name
def p_property_decl(s): def p_property_decl(s):
pos = s.position() pos = s.position()
s.next() # 'property' s.next() # 'property'
name = p_ident(s) name = p_ident(s)
doc, body = p_suite(s, Ctx(level = 'property'), with_doc = 1) doc, body = p_suite_with_docstring(
return Nodes.PropertyNode(pos, name = name, doc = doc, body = body) s, Ctx(level='property'), with_doc_only=True)
return Nodes.PropertyNode(pos, name=name, doc=doc, body=body)
def p_doc_string(s): def p_doc_string(s):
if s.sy == 'BEGIN_STRING': if s.sy == 'BEGIN_STRING':
...@@ -3070,6 +3073,42 @@ def p_doc_string(s): ...@@ -3070,6 +3073,42 @@ def p_doc_string(s):
else: else:
return None return None
def _extract_docstring(node):
"""
Extract a docstring from a statement or from the first statement
in a list. Remove the statement if found. Return a tuple
(plain-docstring or None, node).
"""
doc_node = None
if node is None:
pass
elif isinstance(node, Nodes.ExprStatNode):
if node.expr.is_string_literal:
doc_node = node.expr
node = Nodes.StatListNode(node.pos, stats=[])
elif isinstance(node, Nodes.StatListNode) and node.stats:
stats = node.stats
if isinstance(stats[0], Nodes.ExprStatNode):
if stats[0].expr.is_string_literal:
doc_node = stats[0].expr
del stats[0]
if doc_node is None:
doc = None
elif isinstance(doc_node, ExprNodes.BytesNode):
warning(node.pos,
"Python 3 requires docstrings to be unicode strings")
doc = doc_node.value
elif isinstance(doc_node, ExprNodes.StringNode):
doc = doc_node.unicode_value
if doc is None:
doc = doc_node.value
else:
doc = doc_node.value
return doc, node
def p_code(s, level=None, ctx=Ctx): def p_code(s, level=None, ctx=Ctx):
body = p_statement_list(s, ctx(level = level), first_statement = 1) body = p_statement_list(s, ctx(level = level), first_statement = 1)
if s.sy != 'EOF': if s.sy != 'EOF':
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# More comments # More comments
u'A module docstring' 'A module docstring'
doctest = u"""# Python 3 gets all of these right ... doctest = u"""# Python 3 gets all of these right ...
>>> __doc__ >>> __doc__
...@@ -28,48 +28,44 @@ doctest = u"""# Python 3 gets all of these right ... ...@@ -28,48 +28,44 @@ doctest = u"""# Python 3 gets all of these right ...
Compare with standard Python: Compare with standard Python:
>>> def Pyf(): >>> def Pyf():
... u''' ... '''
... This is a function docstring. ... This is a function docstring.
... ''' ... '''
>>> Pyf.__doc__ >>> Pyf.__doc__
u'\\n This is a function docstring.\\n ' '\\n This is a function docstring.\\n '
>>> class PyC: >>> class PyC:
... u''' ... '''
... This is a class docstring. ... This is a class docstring.
... ''' ... '''
>>> class PyCS(C): >>> class PyCS(C):
... u''' ... '''
... This is a subclass docstring. ... This is a subclass docstring.
... ''' ... '''
>>> class PyCSS(CS): >>> class PyCSS(CS):
... pass ... pass
>>> PyC.__doc__ >>> PyC.__doc__
u'\\n This is a class docstring.\\n ' '\\n This is a class docstring.\\n '
>>> PyCS.__doc__ >>> PyCS.__doc__
u'\\n This is a subclass docstring.\\n ' '\\n This is a subclass docstring.\\n '
>>> PyCSS.__doc__ >>> PyCSS.__doc__
""" """
import sys __test__ = {"test_docstrings" : doctest}
if sys.version_info[0] >= 3:
doctest = doctest.replace(u" u'", u" '")
__test__ = {u"test_docstrings" : doctest}
def f(): def f():
u""" """
This is a function docstring. This is a function docstring.
""" """
class C: class C:
u""" """
This is a class docstring. This is a class docstring.
""" """
class CS(C): class CS(C):
u""" """
This is a subclass docstring. This is a subclass docstring.
""" """
...@@ -77,14 +73,32 @@ class CSS(CS): ...@@ -77,14 +73,32 @@ class CSS(CS):
pass pass
cdef class T: cdef class T:
u""" """
This is an extension type docstring. This is an extension type docstring.
""" """
cdef class TS(T): cdef class TS(T):
u""" """
This is an extension subtype docstring. This is an extension subtype docstring.
""" """
cdef class TSS(TS): cdef class TSS(TS):
pass pass
def n():
"This is not a docstring".lower()
class PyN(object):
u"This is not a docstring".lower()
cdef class CN(object):
b"This is not a docstring".lower()
def test_non_docstrings():
"""
>>> n.__doc__
>>> PyN.__doc__
>>> CN.__doc__
"""
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment