Emile Anclin wrote:
On Friday 01 October 2010 20:15:32 Daniel Harding wrote:
 Currently if pylint is run under Python 2.7 to check a file containing
 dictionary comprehensions or set comprehensions, it results in an
 exception in the astng code.

 The attached patches (against the current hg tip) add support for these
 constructs to astng.

fabulous, this is half the job of making astng py3k compatible!
(or at least an important part)

I will apply the patch right now

Thanks for taking my changes so quickly! However, I realized today that they weren't completely correct. Dict comprehensions and set comprehensions should create their own scope, just like generator expressions. I didn't account for this in my changes, so I have attached a patch against astng that makes DictComp and SetComp subclass from LocalsDictNodeNG rather than NodeNG. I also moved their definitions from node_classes.py to scoped_nodes.py.

Once that change was made, I had to make a change to VariableChecker in pylint to prevent spurious E0602 undefined variable errors. I added visit_*/leave_* methods for dict and set comprehensions which mirror the visit_genexpr/leave_genexpr methods. I also changed the scope type used in visit_genexpr to be 'comprehension' and then reused that for dict and set comprehensions. Attached is a second patch with these changes against pylint.

As I was making these changes, I realized that as part of implementing Python 3 support, both astng and PyLint will need to be able to account for the fact that under Python 2, list comprehensions use the enclosing scope, while under Python 3, they create their own scope. I don't know what the mechanism/architecture will be for making that kind of runtime decision, so I didn't try to tackle that issue.

Also, I hope to implement some tests pylint this week for dict and set comprehensions - I'll post a patch once I have it.

Cheers,

Daniel Harding
# HG changeset patch
# User Daniel Harding <[email protected]>
# Date 1286652831 0
# Node ID 5cde27ab8480580b110aba26f587de25650c5e32
# Parent  12e5f7eefe73dff220e4d17b93aa42eabbc4d8d4
make dictionary comprehensions and set comprehensions have their own scope, 
like generator expressions

diff -r 12e5f7eefe73 -r 5cde27ab8480 node_classes.py
--- a/node_classes.py   Thu Oct 07 18:46:23 2010 +0200
+++ b/node_classes.py   Sat Oct 09 19:33:51 2010 +0000
@@ -505,14 +505,6 @@
         raise IndexError(key)
 
 
-class DictComp(NodeNG):
-    """class representing a DictComp node"""
-    _astng_fields = ('key', 'value', 'generators')
-    key = None
-    value = None
-    generators = None
-
-
 class Discard(StmtMixIn, NodeNG):
     """class representing a Discard node"""
     _astng_fields = ('value',)
@@ -709,13 +701,6 @@
         return self.elts
 
 
-class SetComp(NodeNG):
-    """class representing a SetComp node"""
-    _astng_fields = ('elt', 'generators')
-    elt = None
-    generators = None
-
-
 class Slice(NodeNG):
     """class representing a Slice node"""
     _astng_fields = ('lower', 'upper', 'step')
diff -r 12e5f7eefe73 -r 5cde27ab8480 nodes.py
--- a/nodes.py  Thu Oct 07 18:46:23 2010 +0200
+++ b/nodes.py  Sat Oct 09 19:33:51 2010 +0000
@@ -54,12 +54,13 @@
 from logilab.astng.node_classes import Arguments, AssAttr, Assert, Assign, \
     AssName, AugAssign, Backquote, BinOp, BoolOp, Break, CallFunc, Compare, \
     Comprehension, Const, Continue, Decorators, DelAttr, DelName, Delete, \
-    Dict, DictComp, Discard, Ellipsis, EmptyNode, ExceptHandler, Exec, \
-    ExtSlice, For, From, Getattr, Global, If, IfExp, Import, Index, Keyword, \
-    List, ListComp, Name, Pass, Print, Raise, Return, Set, SetComp, Slice, \
-    Subscript, TryExcept, TryFinally, Tuple, UnaryOp, While, With, Yield, \
+    Dict, Discard, Ellipsis, EmptyNode, ExceptHandler, Exec, ExtSlice, For, \
+    From, Getattr, Global, If, IfExp, Import, Index, Keyword, \
+    List, ListComp, Name, Pass, Print, Raise, Return, Set, Slice, Subscript, \
+    TryExcept, TryFinally, Tuple, UnaryOp, While, With, Yield, \
     const_factory
-from logilab.astng.scoped_nodes import Module, GenExpr, Lambda, Function, Class
+from logilab.astng.scoped_nodes import Module, GenExpr, Lambda, DictComp, \
+    SetComp, Function, Class
 
 ALL_NODE_CLASSES = (
     Arguments, AssAttr, Assert, Assign, AssName, AugAssign,
diff -r 12e5f7eefe73 -r 5cde27ab8480 scoped_nodes.py
--- a/scoped_nodes.py   Thu Oct 07 18:46:23 2010 +0200
+++ b/scoped_nodes.py   Sat Oct 09 19:33:51 2010 +0000
@@ -19,7 +19,7 @@
 # with logilab-astng. If not, see <http://www.gnu.org/licenses/>.
 """This module contains the classes for "scoped" node, i.e. which are opening a
 new local scope in the language definition : Module, Class, Function (and
-Lambda and GenExpr to some extends).
+Lambda, GenExpr, DictComp and SetComp to some extent).
 
 """
 from __future__ import generators
@@ -118,7 +118,7 @@
 
     def scope(self):
         """return the first node defining a new scope (i.e. Module,
-        Function, Class, Lambda but also GenExpr)
+        Function, Class, Lambda but also GenExpr, DictComp and SetComp)
         """
         return self
 
@@ -415,6 +415,33 @@
 GenExpr.scope_lookup = LocalsDictNodeNG._scope_lookup
 
 
+class DictComp(LocalsDictNodeNG):
+    _astng_fields = ('key', 'value', 'generators')
+
+    def __init__(self):
+        self.locals = {}
+        self.key = None
+        self.value = None
+        self.generators = []
+
+    def frame(self):
+        return self.parent.frame()
+DictComp.scope_lookup = LocalsDictNodeNG._scope_lookup
+
+
+class SetComp(LocalsDictNodeNG):
+    _astng_fields = ('elt', 'generators')
+
+    def __init__(self):
+        self.locals = {}
+        self.elt = None
+        self.generators = []
+
+    def frame(self):
+        return self.parent.frame()
+SetComp.scope_lookup = LocalsDictNodeNG._scope_lookup
+
+
 # Function  ###################################################################
 
 
# HG changeset patch
# User Daniel Harding <[email protected]>
# Date 1286653946 0
# Node ID 0e616ba6d5042252524396c62469c6ced8f1ad79
# Parent  fc7b47be6123e8b37f5c9db35474f165d514a878
update VariablesChecker to take into account the nested scopes of dictionary 
and set comprehensions

diff -r fc7b47be6123 -r 0e616ba6d504 checkers/variables.py
--- a/checkers/variables.py     Sat Oct 09 19:51:05 2010 +0000
+++ b/checkers/variables.py     Sat Oct 09 19:52:26 2010 +0000
@@ -181,7 +181,7 @@
     def visit_genexpr(self, node):
         """visit genexpr: update consumption analysis variable
         """
-        self._to_consume.append((copy(node.locals), {}, 'genexpr'))
+        self._to_consume.append((copy(node.locals), {}, 'comprehension'))
 
     def leave_genexpr(self, _):
         """leave genexpr: update consumption analysis variable
@@ -189,6 +189,28 @@
         # do not check for not used locals here
         self._to_consume.pop()
 
+    def visit_dictcomp(self, node):
+        """visit dictcomp: update consumption analysis variable
+        """
+        self._to_consume.append((copy(node.locals), {}, 'comprehension'))
+
+    def leave_dictcomp(self, _):
+        """leave dictcomp: update consumption analysis variable
+        """
+        # do not check for not used locals here
+        self._to_consume.pop()
+
+    def visit_setcomp(self, node):
+        """visit setcomp: update consumption analysis variable
+        """
+        self._to_consume.append((copy(node.locals), {}, 'comprehension'))
+
+    def leave_setcomp(self, _):
+        """leave setcomp: update consumption analysis variable
+        """
+        # do not check for not used locals here
+        self._to_consume.pop()
+
     def visit_function(self, node):
         """visit function: update consumption analysis variable and check 
locals
         """
@@ -349,9 +371,9 @@
             # scope, ignore it. This prevents to access this scope instead of
             # the globals one in function members when there are some common
             # names. The only exception is when the starting scope is a
-            # genexpr and its direct outer scope is a class
+            # comprehension and its direct outer scope is a class
             if scope_type == 'class' and i != start_index and not (
-                base_scope_type == 'genexpr' and i == start_index-1):
+                base_scope_type == 'comprehension' and i == start_index-1):
                 # XXX find a way to handle class scope in a smoother way
                 continue
             # the name has already been consumed, only check it's not a loop
_______________________________________________
Python-Projects mailing list
[email protected]
http://lists.logilab.org/mailman/listinfo/python-projects

Reply via email to