Commit 00209341 authored by Dag Sverre Seljebotn's avatar Dag Sverre Seljebotn

Correctly giving compiler errors on global/attribute buffers

Also, do not stop compilation on first buffer-related error.
parent ddc455d5
...@@ -330,6 +330,9 @@ class Context: ...@@ -330,6 +330,9 @@ class Context:
verbose_flag = options.show_version, verbose_flag = options.show_version,
cplus = options.cplus) cplus = options.cplus)
def nonfatal_error(self, exc):
return Errors.report_error(exc)
def run_pipeline(self, pipeline, source): def run_pipeline(self, pipeline, source):
errors_occurred = False errors_occurred = False
data = source data = source
......
...@@ -83,7 +83,7 @@ ERR_BUF_MISSING = '"%s" missing' ...@@ -83,7 +83,7 @@ ERR_BUF_MISSING = '"%s" missing'
ERR_BUF_INT = '"%s" must be an integer' ERR_BUF_INT = '"%s" must be an integer'
ERR_BUF_NONNEG = '"%s" must be non-negative' ERR_BUF_NONNEG = '"%s" must be non-negative'
ERR_CDEF_INCLASS = 'Cannot assign default value to cdef class attributes' ERR_CDEF_INCLASS = 'Cannot assign default value to cdef class attributes'
ERR_BUF_LOCALONLY = 'Buffer types only allowed as function local variables'
class PostParse(CythonTransform): class PostParse(CythonTransform):
""" """
Basic interpretation of the parse tree, as well as validity Basic interpretation of the parse tree, as well as validity
...@@ -106,19 +106,25 @@ class PostParse(CythonTransform): ...@@ -106,19 +106,25 @@ class PostParse(CythonTransform):
""" """
# Track our context. # Track our context.
in_class_body = False scope_type = None # can be either of 'module', 'function', 'class'
def visit_ModuleNode(self, node):
self.scope_type = 'module'
self.visitchildren(node)
return node
def visit_ClassDefNode(self, node): def visit_ClassDefNode(self, node):
prev = self.in_class_body prev = self.scope_type
self.in_class_body = True self.scope_type = 'class'
self.visitchildren(node) self.visitchildren(node)
self.in_class_body = prev self.scope_type = prev
return node return node
def visit_FuncDefNode(self, node): def visit_FuncDefNode(self, node):
prev = self.in_class_body prev = self.scope_type
self.in_class_body = False self.scope_type = 'function'
self.visitchildren(node) self.visitchildren(node)
self.in_class_body = prev self.scope_type = prev
return node return node
# cdef variables # cdef variables
...@@ -127,14 +133,20 @@ class PostParse(CythonTransform): ...@@ -127,14 +133,20 @@ class PostParse(CythonTransform):
# declaration. Also, it makes use of the fact that a cdef decl # declaration. Also, it makes use of the fact that a cdef decl
# must appear before the first use, so we don't have to deal with # must appear before the first use, so we don't have to deal with
# "i = 3; cdef int i = i" and can simply move the nodes around. # "i = 3; cdef int i = i" and can simply move the nodes around.
try:
self.visitchildren(node) self.visitchildren(node)
except PostParseError, e:
# An error in a cdef clause is ok, simply remove the declaration
# and try to move on to report more errors
self.context.nonfatal_error(e)
return None
stats = [node] stats = [node]
for decl in node.declarators: for decl in node.declarators:
while isinstance(decl, CPtrDeclaratorNode): while isinstance(decl, CPtrDeclaratorNode):
decl = decl.base decl = decl.base
if isinstance(decl, CNameDeclaratorNode): if isinstance(decl, CNameDeclaratorNode):
if decl.default is not None: if decl.default is not None:
if self.in_class_body: if self.scope_type == 'class':
raise PostParseError(decl.pos, ERR_CDEF_INCLASS) raise PostParseError(decl.pos, ERR_CDEF_INCLASS)
stats.append(SingleAssignmentNode(node.pos, stats.append(SingleAssignmentNode(node.pos,
lhs=NameNode(node.pos, name=decl.name), lhs=NameNode(node.pos, name=decl.name),
...@@ -145,10 +157,13 @@ class PostParse(CythonTransform): ...@@ -145,10 +157,13 @@ class PostParse(CythonTransform):
# buffer access # buffer access
buffer_options = ("dtype", "ndim") # ordered! buffer_options = ("dtype", "ndim") # ordered!
def visit_CBufferAccessTypeNode(self, node): def visit_CBufferAccessTypeNode(self, node):
if not self.scope_type == 'function':
raise PostParseError(node.pos, ERR_BUF_LOCALONLY)
options = {} options = {}
# Fetch positional arguments # Fetch positional arguments
if len(node.positional_args) > len(self.buffer_options): if len(node.positional_args) > len(self.buffer_options):
self.context.error(ERR_BUF_TOO_MANY) raise PostParseError(node.pos, ERR_BUF_TOO_MANY)
for arg, unicode_name in zip(node.positional_args, self.buffer_options): for arg, unicode_name in zip(node.positional_args, self.buffer_options):
name = str(unicode_name) name = str(unicode_name)
options[name] = arg options[name] = arg
...@@ -156,17 +171,16 @@ class PostParse(CythonTransform): ...@@ -156,17 +171,16 @@ class PostParse(CythonTransform):
for item in node.keyword_args.key_value_pairs: for item in node.keyword_args.key_value_pairs:
name = str(item.key.value) name = str(item.key.value)
if not name in self.buffer_options: if not name in self.buffer_options:
raise PostParseError(item.key.pos, raise PostParseError(item.key.pos, ERR_BUF_OPTION_UNKNOWN % name)
ERR_BUF_UNKNOWN % name)
if name in options.keys(): if name in options.keys():
raise PostParseError(item.key.pos, raise PostParseError(item.key.pos, ERR_BUF_DUP % key)
ERR_BUF_DUP % key)
options[name] = item.value options[name] = item.value
provided = options.keys() provided = options.keys()
# get dtype # get dtype
dtype = options.get("dtype") dtype = options.get("dtype")
if dtype is None: raise PostParseError(node.pos, ERR_BUF_MISSING % 'dtype') if dtype is None:
raise PostParseError(node.pos, ERR_BUF_MISSING % 'dtype')
node.dtype_node = dtype node.dtype_node = dtype
# get ndim # get ndim
......
...@@ -47,21 +47,35 @@ class TestBufferParsing(CythonTest): ...@@ -47,21 +47,35 @@ class TestBufferParsing(CythonTest):
self.not_parseable("Non-keyword arg following keyword arg", self.not_parseable("Non-keyword arg following keyword arg",
u"cdef object[foo=1, 2] x") u"cdef object[foo=1, 2] x")
# See also tests/error/e_bufaccess.pyx and tets/run/bufaccess.pyx
class TestBufferOptions(CythonTest): class TestBufferOptions(CythonTest):
# Tests the full parsing of the options within the brackets # Tests the full parsing of the options within the brackets
def parse_opts(self, opts): def nonfatal_error(self, error):
s = u"cdef object[%s] x" % opts # We're passing self as context to transform to trap this
root = self.fragment(s, pipeline=[PostParse(self)]).root self.error = error
buftype = root.stats[0].base_type self.assert_(self.expect_error)
def parse_opts(self, opts, expect_error=False):
s = u"def f():\n cdef object[%s] x" % opts
self.expect_error = expect_error
root = self.fragment(s, pipeline=[NormalizeTree(self), PostParse(self)]).root
if not expect_error:
vardef = root.stats[0].body.stats[0]
assert isinstance(vardef, CVarDefNode) # use normal assert as this is to validate the test code
buftype = vardef.base_type
self.assert_(isinstance(buftype, CBufferAccessTypeNode)) self.assert_(isinstance(buftype, CBufferAccessTypeNode))
self.assert_(isinstance(buftype.base_type_node, CSimpleBaseTypeNode)) self.assert_(isinstance(buftype.base_type_node, CSimpleBaseTypeNode))
self.assertEqual(u"object", buftype.base_type_node.name) self.assertEqual(u"object", buftype.base_type_node.name)
return buftype return buftype
else:
self.assert_(len(root.stats[0].body.stats) == 0)
def non_parse(self, expected_err, opts): def non_parse(self, expected_err, opts):
e = self.should_fail(lambda: self.parse_opts(opts)) self.parse_opts(opts, expect_error=True)
self.assertEqual(expected_err, e.message_only) # e = self.should_fail(lambda: self.parse_opts(opts))
self.assertEqual(expected_err, self.error.message_only)
def test_basic(self): def test_basic(self):
buf = self.parse_opts(u"unsigned short int, 3") buf = self.parse_opts(u"unsigned short int, 3")
...@@ -86,10 +100,12 @@ class TestBufferOptions(CythonTest): ...@@ -86,10 +100,12 @@ class TestBufferOptions(CythonTest):
def test_use_DEF(self): def test_use_DEF(self):
t = self.fragment(u""" t = self.fragment(u"""
DEF ndim = 3 DEF ndim = 3
def f():
cdef object[int, ndim] x cdef object[int, ndim] x
cdef object[ndim=ndim, dtype=int] y cdef object[ndim=ndim, dtype=int] y
""", pipeline=[PostParse(self)]).root """, pipeline=[NormalizeTree(self), PostParse(self)]).root
self.assert_(t.stats[1].base_type.ndim == 3) stats = t.stats[0].body.stats
self.assert_(t.stats[2].base_type.ndim == 3) self.assert_(stats[0].base_type.ndim == 3)
self.assert_(stats[1].base_type.ndim == 3)
# add exotic and impossible combinations as they come along # add exotic and impossible combinations as they come along...
cdef object[int] buf
cdef class A:
cdef object[int] buf
def f():
cdef object[fakeoption=True] buf1
cdef object[int, -1] buf1b
cdef object[ndim=-1] buf2
cdef object[int, 'a'] buf3
cdef object[int,2,3,4,5,6] buf4
_ERRORS = u"""
1:11: Buffer types only allowed as function local variables
3:15: Buffer types only allowed as function local variables
6:27: "fakeoption" is not a buffer option
7:22: "ndim" must be non-negative
8:15: "dtype" missing
9:21: "ndim" must be an integer
10:15: Too many buffer options
"""
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