#479, sub-directives via keywords
authorRobert Bradshaw <robertwb@math.washington.edu>
Fri, 22 Jan 2010 00:17:53 +0000 (16:17 -0800)
committerRobert Bradshaw <robertwb@math.washington.edu>
Fri, 22 Jan 2010 00:17:53 +0000 (16:17 -0800)
Cython/Compiler/Options.py
Cython/Compiler/ParseTreeTransforms.py
Cython/Compiler/Parsing.py
tests/compile/c_directives.pyx

index 9fccdf74c8ed712bf8ad34cc68dd2da2fe5827b9..6019c6ca8ca90802bb1ceb07727f2d632a7519cb 100644 (file)
@@ -64,6 +64,9 @@ directive_defaults = {
     'profile': False,
     'infer_types': False,
     'autotestdict': True,
+    
+    'warn': None,
+    'warn.undeclared': False,
 
 # test support
     'test_assert_path_exists' : [],
index 3e00106bd64a97f6517a6404c0a3dbc1e975ecb6..c302210a419f7818d719cd946173f785986a2e9c 100644 (file)
@@ -427,55 +427,74 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
             node.cython_attribute = self.directive_names.get(node.name)
         return node
 
-    def try_to_parse_directive(self, node):
+    def try_to_parse_directives(self, node):
         # If node is the contents of an directive (in a with statement or
-        # decorator), returns (directivename, value).
+        # decorator), returns a list of (directivename, value) pairs.
         # Otherwise, returns None
-        optname = None
         if isinstance(node, CallNode):
             self.visit(node.function)
             optname = node.function.as_cython_attribute()
-
-        if optname:
-            directivetype = Options.directive_types.get(optname)
-            if directivetype:
-                args, kwds = node.explicit_args_kwds()
-                if optname == 'infer_types':
-                    if kwds is not None or len(args) != 1:
-                        raise PostParseError(node.function.pos,
-                            'The %s directive takes one compile-time boolean argument' % optname)
-                    elif isinstance(args[0], BoolNode):
-                        return (optname, args[0].value)
-                    elif isinstance(args[0], NoneNode):
-                        return (optname, None)
-                    else:
-                        raise PostParseError(node.function.pos,
-                            'The %s directive takes one compile-time boolean argument' % optname)
-                elif directivetype is bool:
-                    if kwds is not None or len(args) != 1 or not isinstance(args[0], BoolNode):
-                        raise PostParseError(node.function.pos,
-                            'The %s directive takes one compile-time boolean argument' % optname)
-                    return (optname, args[0].value)
-                elif directivetype is str:
-                    if kwds is not None or len(args) != 1 or not isinstance(args[0], (StringNode, UnicodeNode)):
-                        raise PostParseError(node.function.pos,
-                            'The %s directive takes one compile-time string argument' % optname)
-                    return (optname, str(args[0].value))
-                elif directivetype is dict:
-                    if len(args) != 0:
-                        raise PostParseError(node.function.pos,
-                            'The %s directive takes no prepositional arguments' % optname)
-                    return optname, dict([(key.value, value) for key, value in kwds.key_value_pairs])
-                elif directivetype is list:
-                    if kwds and len(kwds) != 0:
-                        raise PostParseError(node.function.pos,
-                            'The %s directive takes no keyword arguments' % optname)
-                    return optname, [ str(arg.value) for arg in args ]
-                else:
-                    assert False
-
+            if optname:
+                directivetype = Options.directive_types.get(optname)
+                if directivetype:
+                    args, kwds = node.explicit_args_kwds()
+                    directives = []
+                    key_value_pairs = []
+                    if kwds is not None and directivetype is not dict:
+                        for keyvalue in kwds.key_value_pairs:
+                            key, value = keyvalue
+                            sub_optname = "%s.%s" % (optname, key.value)
+                            if Options.directive_types.get(sub_optname):
+                                directives.append(self.try_to_parse_directive(sub_optname, [value], None, keyvalue.pos))
+                            else:
+                                key_value_pairs.append(keyvalue)
+                        if not key_value_pairs:
+                            kwds = None
+                        else:
+                            kwds.key_value_pairs = key_value_pairs
+                        if directives and not kwds and not args:
+                            return directives
+                    directives.append(self.try_to_parse_directive(optname, args, kwds, node.function.pos))
+                    return directives
+                
         return None
 
+    def try_to_parse_directive(self, optname, args, kwds, pos):
+        directivetype = Options.directive_types.get(optname)
+        if optname == 'infer_types':
+            if kwds is not None or len(args) != 1:
+                raise PostParseError(pos,
+                    'The %s directive takes one compile-time boolean argument' % optname)
+            elif isinstance(args[0], BoolNode):
+                return (optname, args[0].value)
+            elif isinstance(args[0], NoneNode):
+                return (optname, None)
+            else:
+                raise PostParseError(pos,
+                    'The %s directive takes one compile-time boolean argument' % optname)
+        elif directivetype is bool:
+            if kwds is not None or len(args) != 1 or not isinstance(args[0], BoolNode):
+                raise PostParseError(pos,
+                    'The %s directive takes one compile-time boolean argument' % optname)
+            return (optname, args[0].value)
+        elif directivetype is str:
+            if kwds is not None or len(args) != 1 or not isinstance(args[0], (StringNode, UnicodeNode)):
+                raise PostParseError(pos,
+                    'The %s directive takes one compile-time string argument' % optname)
+            return (optname, str(args[0].value))
+        elif directivetype is dict:
+            if len(args) != 0:
+                raise PostParseError(pos,
+                    'The %s directive takes no prepositional arguments' % optname)
+            return optname, dict([(key.value, value) for key, value in kwds.key_value_pairs])
+        elif directivetype is list:
+            if kwds and len(kwds) != 0:
+                raise PostParseError(pos,
+                    'The %s directive takes no keyword arguments' % optname)
+            return optname, [ str(arg.value) for arg in args ]
+        else:
+            assert False
+
     def visit_with_directives(self, body, directives):
         olddirectives = self.directives
         newdirectives = copy.copy(olddirectives)
@@ -495,9 +514,9 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
             # Split the decorators into two lists -- real decorators and directives
             realdecs = []
             for dec in node.decorators:
-                directive = self.try_to_parse_directive(dec.decorator)
-                if directive is not None:
-                    directives.append(directive)
+                new_directives = self.try_to_parse_directives(dec.decorator)
+                if new_directives is not None:
+                    directives.extend(new_directives)
                 else:
                     realdecs.append(dec)
             if realdecs and isinstance(node, CFuncDefNode):
@@ -533,26 +552,28 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
     def visit_CVarDefNode(self, node):
         if node.decorators:
             for dec in node.decorators:
-                directive = self.try_to_parse_directive(dec.decorator)
-                if directive is not None and directive[0] == u'locals':
-                    node.directive_locals = directive[1]
-                else:
-                    self.context.nonfatal_error(PostParseError(dec.pos,
-                        "Cdef functions can only take cython.locals() decorator."))
-                    continue
+                for directive in self.try_to_parse_directives(dec.decorator) or []:
+                    if directive is not None and directive[0] == u'locals':
+                        node.directive_locals = directive[1]
+                    else:
+                        self.context.nonfatal_error(PostParseError(dec.pos,
+                            "Cdef functions can only take cython.locals() decorator."))
         return node
                                    
     # Handle with statements
     def visit_WithStatNode(self, node):
-        directive = self.try_to_parse_directive(node.manager)
-        if directive is not None:
-            if node.target is not None:
-                self.context.nonfatal_error(
-                    PostParseError(node.pos, "Compiler directive with statements cannot contain 'as'"))
-            else:
-                name, value = directive
-                if self.check_directive_scope(node.pos, name, 'with statement'):
-                    return self.visit_with_directives(node.body, {name:value})
+        directive_dict = {}
+        for directive in self.try_to_parse_directives(node.manager) or []:
+            if directive is not None:
+                if node.target is not None:
+                    self.context.nonfatal_error(
+                        PostParseError(node.pos, "Compiler directive with statements cannot contain 'as'"))
+                else:
+                    name, value = directive
+                    if self.check_directive_scope(node.pos, name, 'with statement'):
+                        directive_dict[name] = value
+        if directive_dict:
+            return self.visit_with_directives(node.body, directive_dict)
         return self.visit_Node(node)
 
 class WithTransform(CythonTransform, SkipDeclarations):
index d459b4b24139ef5806414dfe74b35d04546b260c..a83bcc0517b76851c1ecaf473f5cab040889bbc7 100644 (file)
@@ -2579,7 +2579,7 @@ def p_code(s, level=None):
             repr(s.sy), repr(s.systring)))
     return body
 
-COMPILER_DIRECTIVE_COMMENT_RE = re.compile(r"^#\s*cython:\s*(\w+\s*=.*)$")
+COMPILER_DIRECTIVE_COMMENT_RE = re.compile(r"^#\s*cython:\s*((\w|[.])+\s*=.*)$")
 
 def p_compiler_directive_comments(s):
     result = {}
index f6158976e03ac7042ab359afa47dbd5c033a7f9a..942ba90ec841b0c8722ad9605e76478260af2786 100644 (file)
@@ -1,5 +1,6 @@
 # cython: boundscheck  =  False
 # cython: ignoreme = OK
+# cython: warn.undeclared = False
 
 # This testcase is most useful if you inspect the generated C file
 
@@ -32,3 +33,8 @@ def i(object[int] buf):
     with bc(True):
         print buf[3] # bs
     
+from cython cimport warn as my_warn
+
+@my_warn(undeclared=True)
+def j():
+    pass