Repository: serge-sans-paille/beniget

Issue:
AnnAssign links the target as beeing the def of dannotation
The little it of code seems strange to https://github.com/serge-sans-paille/beniget/blob/master/beniget/beniget.py#L769-L772

I think the annotation should not be added to the uses of the target of the assignment. It's like saying "the assign target is where it's annotation is defined", which doesn't make any sens to me. 

~~That way we could cleanup the extra ``lambda``/``None`` values in the ``_defered_annotations`` stack as well.~~ (we need it for #72 

Tell me what you think, @serge-sans-paille. 

Discussion:

Patch:
diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml
index 3ac9eef..9be07c2 100644
--- a/.github/workflows/core.yml
+++ b/.github/workflows/core.yml
@@ -13,8 +13,6 @@ jobs:
       matrix:
           python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12-dev"]
           include:
-            - python-version: 2.7
-              container: python:2.7.18-buster
             - python-version: 3.6
               os: ubuntu-20.04
     runs-on: ${{ matrix.os || 'ubuntu-latest' }}
@@ -22,7 +20,6 @@ jobs:
     steps:
     - uses: actions/checkout@v3
     - name: Setup Python ${{ matrix.python-version }}
-      if: ${{ ! ( matrix.python-version == '2.7' ) }}
       uses: actions/setup-python@v4
       with:
         python-version: ${{ matrix.python-version }}
diff --git a/beniget/beniget.py b/beniget/beniget.py
index efe40bb..9f45161 100644
--- a/beniget/beniget.py
+++ b/beniget/beniget.py
@@ -1,39 +1,10 @@
-from collections import defaultdict, OrderedDict
+from collections import defaultdict
 from contextlib import contextmanager
 import sys
 
 import gast as ast
 
-# TODO: remove me when python 2 is not supported anymore
-class _ordered_set(object):
-    def __init__(self, elements=None):
-        self.values = OrderedDict.fromkeys(elements or [])
-
-    def add(self, value):
-        self.values[value] = None
-
-    def update(self, values):
-        self.values.update((k, None) for k in values)
-
-    def __iter__(self):
-        return iter(self.values.keys())
-
-    def __contains__(self, value):
-        return value in self.values
-
-    def __add__(self, other):
-        out = self.values.copy()
-        out.update(other.values)
-        return out
-
-    def __len__(self):
-        return len(self.values)
-
-if sys.version_info >= (3,6):
-    from .ordered_set import ordered_set
-else:
-    # python < 3,6 we fall back on older version of the ordered_set
-    ordered_set = _ordered_set
+from .ordered_set import ordered_set
 
 class Ancestors(ast.NodeVisitor):
     """
@@ -160,11 +131,8 @@ class Def(object):
             )
 
 
-if sys.version_info.major == 2:
-    BuiltinsSrc = __builtins__
-else:
-    import builtins
-    BuiltinsSrc = builtins.__dict__
+import builtins
+BuiltinsSrc = builtins.__dict__
 
 Builtins = {k: v for k, v in BuiltinsSrc.items()}
 
@@ -242,9 +210,8 @@ class CollectLocals(ast.NodeVisitor):
     def skip(self, _):
         pass
 
-    if sys.version_info.major >= 3:
-        visit_SetComp = visit_DictComp = visit_ListComp = skip
-        visit_GeneratorExp = skip
+    visit_SetComp = visit_DictComp = visit_ListComp = skip
+    visit_GeneratorExp = skip
 
     visit_Lambda = skip
 
@@ -529,13 +496,7 @@ class DefUseChains(ast.NodeVisitor):
         self._scope_depths.pop()
         self._scopes.pop()
 
-    if sys.version_info.major >= 3:
-        CompScopeContext = ScopeContext
-    else:
-        @contextmanager
-        def CompScopeContext(self, node):
-            yield
-
+    CompScopeContext = ScopeContext
 
     @contextmanager
     def DefinitionContext(self, definitions):
@@ -785,18 +746,13 @@ class DefUseChains(ast.NodeVisitor):
 
     def visit_AnnAssign(self, node):
         if node.value:
-            dvalue = self.visit(node.value)
+            self.visit(node.value)
         if not self.future_annotations:
-            dannotation = self.visit(node.annotation)
+            self.visit(node.annotation)
         else:
             self._defered_annotations[-1].append(
-                (node.annotation, list(self._scopes),
-                lambda d:dtarget.add_user(d)))
-        dtarget = self.visit(node.target)
-        if not self.future_annotations:
-            dtarget.add_user(dannotation)
-        if node.value:
-            dvalue.add_user(dtarget)
+                (node.annotation, list(self._scopes), None))
+        self.visit(node.target)
 
     def visit_AugAssign(self, node):
         dvalue = self.visit(node.value)
@@ -818,12 +774,6 @@ class DefUseChains(ast.NodeVisitor):
         else:
             self.visit(node.target).add_user(dvalue)
 
-    def visit_Print(self, node):
-        if node.dest:
-            self.visit(node.dest)
-        for value in node.values:
-            self.visit(value)
-
     def visit_For(self, node):
         self.visit(node.iter)
 
@@ -990,32 +940,6 @@ class DefUseChains(ast.NodeVisitor):
                 self.set_definition(alias.asname or alias.name, dalias)
             self.add_to_locals(alias.asname or alias.name, dalias)
 
-    def visit_Exec(self, node):
-        dnode = self.chains.setdefault(node, Def(node))
-        self.visit(node.body)
-
-        if node.globals:
-            self.visit(node.globals)
-        else:
-            # any global may be used by this exec!
-            for defs in self._definitions[0].values():
-                for d in defs:
-                    d.add_user(dnode)
-
-        if node.locals:
-            self.visit(node.locals)
-        else:
-            # any local may be used by this exec!
-            visible_locals = set()
-            for _definitions in reversed(self._definitions[1:]):
-                for dname, defs in _definitions.items():
-                    if dname not in visible_locals:
-                        visible_locals.add(dname)
-                        for d in defs:
-                            d.add_user(dnode)
-
-        self.extend_definition("*", dnode)
-
     def visit_Global(self, node):
         for name in node.names:
             self._globals[-1].add(name)
@@ -1300,7 +1224,7 @@ class DefUseChains(ast.NodeVisitor):
 
     def visit_comprehension(self, node, is_nested):
         dnode = self.chains.setdefault(node, Def(node))
-        if not is_nested and sys.version_info.major >= 3:
+        if not is_nested:
             # There's one part of a comprehension or generator expression that executes in the surrounding scope, 
             # it's the expression for the outermost iterable.
             with self.SwitchScopeContext(self._definitions[:-1], self._scopes[:-1], 
diff --git a/beniget/ordered_set.py b/beniget/ordered_set.py
index 8ce5e81..4a2132c 100644
--- a/beniget/ordered_set.py
+++ b/beniget/ordered_set.py
@@ -30,9 +30,6 @@ Copied from https://github.com/bustawin/ordered-set-37
 
 import sys
 
-if sys.version_info < (3,6):
-    raise ImportError('ordered_set requires python 3.6 or later')
-
 from collections import OrderedDict
 import itertools
 from typing import TYPE_CHECKING, MutableSet
diff --git a/setup.py b/setup.py
index ec9c76f..ab5f998 100644
--- a/setup.py
+++ b/setup.py
@@ -18,7 +18,7 @@ setup(
     packages=["beniget"],
     description="Extract semantic information about static Python code",
     long_description="""
-A static analyzer for Python2 and Python3 code.
+A static analyzer for Python code.
 
 Beniget provides a static over-approximation of the global and
 local definitions inside Python Module/Class/Function.
@@ -34,13 +34,8 @@ It can also compute def-use chains from each definition.""",
         "Intended Audience :: Developers",
         "License :: OSI Approved :: BSD License",
         "Natural Language :: English",
-        "Programming Language :: Python :: 2",
-        "Programming Language :: Python :: 2.7",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.4",
-        "Programming Language :: Python :: 3.5",
-        "Programming Language :: Python :: 3.6",
     ],
-    python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
+    python_requires=">=3.6",
     **kw
 )


Test patch:
diff --git a/tests/test_chains.py b/tests/test_chains.py
index ff309e7..a388867 100644
--- a/tests/test_chains.py
+++ b/tests/test_chains.py
@@ -11,10 +11,7 @@ unittest.util._MAX_LENGTH=2000
 
 @contextmanager
 def captured_output():
-    if sys.version_info.major >= 3:
-        new_out, new_err = io.StringIO(), io.StringIO()
-    else:
-        new_out, new_err = io.BytesIO(), io.BytesIO()
+    new_out, new_err = io.StringIO(), io.StringIO()
     old_out, old_err = sys.stdout, sys.stderr
     try:
         sys.stdout, sys.stderr = new_out, new_err
@@ -75,10 +72,9 @@ class TestDefUseChains(TestCase):
         code = "for a, b in ((1,2), (3,4)): a"
         self.checkChains(code, ["a -> (a -> ())", "b -> ()"])
 
-    if sys.version_info.major >= 3:
-        def test_type_destructuring_starred(self):
-            code = "a, *b = range(2); b"
-            self.checkChains(code, ['a -> ()', 'b -> (b -> ())'])
+    def test_type_destructuring_starred(self):
+        code = "a, *b = range(2); b"
+        self.checkChains(code, ['a -> ()', 'b -> (b -> ())'])
 
     def test_assign_in_loop(self):
         code = "a = 2\nwhile 1: a = 1\na"
@@ -147,10 +143,7 @@ for _ in [1]:
 
     def test_simple_print(self):
         code = "a = 1; print(a)"
-        if sys.version_info.major >= 3:
-            self.checkChains(code, ["a -> (a -> (Call -> ()))"])
-        else:
-            self.checkChains(code, ["a -> (a -> ())"])
+        self.checkChains(code, ["a -> (a -> (Call -> ()))"])
 
     def test_simple_redefinition(self):
         code = "a = 1; a + 2; a = 3; +a"
@@ -227,22 +220,14 @@ for _ in [1]:
 
     def test_straight_raise(self):
         code = "raise next([e for e in [1]])"
-        if sys.version_info.major >= 3:
-            self.checkChains(code, [])
-        else:
-            self.checkChains(code, ['e -> (e -> (ListComp -> (Call -> ())))'])
+        self.checkChains(code, [])
 
     def test_redefinition_in_comp(self):
         code = "[name for name in 'hello']\nfor name in 'hello':name"
-        if sys.version_info.major >= 3:
-            self.checkChains(
+        self.checkChains(
                 code,
                 ['name -> (name -> ())'])
-        else:
-            self.checkChains(
-                code,
-                ['name -> (name -> (ListComp -> ()))', 'name -> (name -> ())'])
-
+        
     def test_nested_while(self):
         code = '''
 done = 1
@@ -411,7 +396,6 @@ while done:
         code = "class A:pass\nclass B(A):pass"
         self.checkChains(code, ["A -> (A -> (B -> ()))", "B -> ()"])
 
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_def_used_in_self_default(self):
         code = "def foo(x:foo): return foo"
         c = beniget.DefUseChains()
@@ -458,24 +442,17 @@ def middle():
         c = beniget.DefUseChains()
         node = ast.parse(code)
         c.visit(node)
-        if sys.version_info.major >= 3:
-            self.assertEqual(c.dump_chains(node.body[0]), ['a -> ()', 'b -> ()'])
-        else:
-            self.assertEqual(c.dump_chains(node.body[0]),
-                             ['a -> (a -> (ListComp -> ()))', '_ -> ()', 'b -> ()'])
-
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
+        self.assertEqual(c.dump_chains(node.body[0]), ['a -> ()', 'b -> ()'])
+        
     def test_functiondef_returns(self):
         code = "x = 1\ndef foo() -> x: pass"
         self.checkChains(code, ['x -> (x -> ())', 'foo -> ()'])
 
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_arg_annotation(self):
         code = "type_ = int\ndef foo(bar: type_): pass"
         self.checkChains(code, ["type_ -> (type_ -> ())", "foo -> ()"])
 
 
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_annotation_inner_class(self):
 
         code = '''
@@ -531,12 +508,10 @@ def outer():
         self.check_message(code,
                            ["unbound identifier 'x' at <unknown>:3"])
 
-    @skipIf(sys.version_info < (3, 0), 'Python 3 syntax')
     def test_unbound_local_identifier_in_method(self):
         code = "class A:pass\nclass B:\n def A(self) -> A:pass"
         self.check_message(code, [])
 
-    @skipIf(sys.version_info < (3, 0), 'Python 3 syntax')
     def test_unbound_local_identifier_nonlocal(self):
         code = "def A():\n x = 1\n class B: nonlocal x; x = x"
         self.check_message(code, [])
@@ -573,7 +548,6 @@ class Visitor:
                               'Visitor -> ()'])
             self.assertEqual(c.dump_chains(node.body[-1]), ['Attr -> ()'])
     
-    @skipIf(sys.version_info < (3, 0), 'Python 3 syntax')
     def test_star_assignment(self):
         code = '''
 curr, *parts = [1,2,3]
@@ -587,7 +561,6 @@ while curr:
         self.checkChains(code, ['curr -> (curr -> (), curr -> (Call -> ()))', 
                                 'parts -> (parts -> (), parts -> ())']*2)
     
-    @skipIf(sys.version_info < (3, 0), 'Python 3 syntax')
     def test_star_assignment_nested(self):
         code = '''
 (curr, *parts),i = [1,2,3],0
@@ -611,7 +584,6 @@ while curr:
         code = "NameError().name = 't'"
         self.checkChains(code, [])
 
-    @skipIf(sys.version_info < (3, 0), 'Python 3 syntax')
     def test_annotation_uses_class_level_name(self):
         code = '''
 Thing = object
@@ -667,7 +639,6 @@ cos = pop()'''
             'cos -> (cos -> (Call -> ()))'
         ])
     
-    @skipIf(sys.version_info < (3, 0), 'Python 3 semantics')
     def test_class_scope_comprehension(self):
         code = '''
 class Cls:
@@ -685,7 +656,6 @@ class Cls:
                           'foo -> (comprehension -> (GeneratorExp -> ())), '
                           'foo -> (comprehension -> (DictComp -> ())))'])
     
-    @skipIf(sys.version_info < (3, 0), 'Python 3 semantics')
     def test_class_scope_comprehension_invalid(self):
         code = '''
 class Foo:
@@ -778,7 +748,6 @@ class Example:
                                   'W: assignment expression cannot be used in a comprehension iterable expression at <unknown>:15:0',
                                   'W: assignment expression within a comprehension cannot be used in a class body at <unknown>:19:6'])
     
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_annotation_unbound(self):
         code = '''
 def f(x:f) -> f: # 'f' annotations are unbound
@@ -787,7 +756,6 @@ def f(x:f) -> f: # 'f' annotations are unbound
             code, ['f -> ()'], strict=False
         )
     
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_method_annotation_unbound(self):
         code = '''
 class S:
@@ -799,7 +767,6 @@ class S:
         self.assertEqual(chains.dump_chains(mod.body[0]), 
                          ['f -> ()'])
     
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_annotation_unbound_pep563(self):
         code = '''
 from __future__ import annotations
@@ -809,7 +776,6 @@ def f(x:f) -> f: # 'f' annotations are NOT unbound because pep563
             code, ['annotations -> ()', 'f -> (f -> (), f -> ())']
         )
     
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_method_annotation_unbound_pep563(self):
         code = '''
 from __future__ import annotations
@@ -842,7 +808,6 @@ class S:
             code, ['name2 -> (name2 -> ())', '* -> ()']
         )
 
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_annotation_use_upper_scope_variables(self):
         code = '''
 from typing import Union
@@ -862,7 +827,6 @@ class System:
                     'System -> ()',]
         )
     
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_future_annotation_class_var(self):
         code = '''
 from __future__ import annotations
@@ -883,7 +847,6 @@ class System:
             'Attribute -> ()'
         ])
         
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_pep0563_annotations(self):
 
         # code taken from https://peps.python.org/pep-0563/
@@ -946,7 +909,7 @@ Thing:TypeAlias = 'Mapping'
                     'Type -> (Type -> (Subscript -> ()))',
                     'C -> (C -> (Attribute -> ()), C -> (Attribute -> ()), C -> (Attribute -> '
                     '(Attribute -> ())))',
-                    'Thing -> (Thing -> (), Thing -> (Subscript -> ()), TypeAlias -> ())'], 
+                    'Thing -> (Thing -> (), Thing -> (Subscript -> ()))'], 
                 strict=False
             )
         produced_messages = out.getvalue().strip().split("\n")
@@ -965,19 +928,18 @@ Thing:TypeAlias = 'Mapping'
                             'method -> ()',
                             'method -> ()',
                             'method -> ()',
-                            'field -> (field -> (), field -> (), TypeAlias -> ())',
+                            'field -> (field -> (), field -> ())',
                             'D -> (D -> ())'])
         
         # locals of D
         self.assertEqual(c.dump_chains(node.body[-2].body[-1]), 
-                         ['field2 -> (TypeAlias -> (), field2 -> (), field2 -> ())',
+                         ['field2 -> (field2 -> (), field2 -> ())',
                             'method -> ()',
                             'method -> ()',
                             'method -> ()',
                             'method -> ()',
                             'Thing -> ()'])
 
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_pep563_self_referential_annotation(self):
         code = '''
 """
@@ -1012,7 +974,6 @@ class B:
                 strict=False
             )
     
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_wilcard_import_annotation(self):
         code = '''
 from typing import *
@@ -1021,17 +982,16 @@ primes: List[int] # should resolve to the star
 
         self.checkChains(
                 code, 
-                ['* -> (List -> (Subscript -> ()))', 'primes -> (Subscript -> ())'],
+                ['* -> (List -> (Subscript -> ()))', 'primes -> ()'],
                 strict=False
             )
         # same with 'from __future__ import annotations'
         self.checkChains(
                 'from __future__ import annotations\n' + code, 
-                ['annotations -> ()', '* -> (List -> (Subscript -> ()))', 'primes -> (Subscript -> ())'],
+                ['annotations -> ()', '* -> (List -> (Subscript -> ()))', 'primes -> ()'],
                 strict=False
             )
     
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_wilcard_import_annotation_and_global_scope(self):
         # we might argue that it should resolve to both the wildcard 
         # defined name and the type alias, but we're currently ignoring these
@@ -1047,12 +1007,11 @@ List = list
                 code, 
                 ['annotations -> ()',
                 '* -> ()',
-                'primes -> (Subscript -> ())',
+                'primes -> ()',
                 'List -> (List -> (Subscript -> ()))'],
                 strict=False
             )
     
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_annotation_in_functions_locals(self):
         
         code = '''
@@ -1089,7 +1048,6 @@ X = generate()
                          ['A -> ()', 
                           'C -> (C -> ())'])
 
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_annotation_in_inner_functions_locals(self):
 
         code = '''
@@ -1167,7 +1125,6 @@ fn = outer()
         with self.assertRaises(ValueError, msg='invalid heads: must include at least one element'):
             _get_lookup_scopes(())
 
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_annotation_inner_inner_fn(self):
         code = '''
 def outer():
@@ -1194,7 +1151,6 @@ def outer():
                           'mytype -> (mytype -> ())'])
     
 
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_annotation_very_nested(self):
         
         # this code does not produce any pyright warnings
@@ -1263,7 +1219,6 @@ fn = outer()
                                      'mytype -> (), '
                                      'mytype -> ())'])
     
-    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
     def test_pep563_type_alias_override_class(self):
         code = '''
 from __future__ import annotations
@@ -1281,6 +1236,12 @@ A = bytes
                  'A -> (A -> (), A -> ())'], # good
                 strict=False
             )
+    
+    @skipIf(sys.version_info.major < 3, "Python 3 syntax")
+    def test_annotation_def_is_not_assign_target(self):
+        code = 'from typing import Optional; var:Optional'
+        self.checkChains(code, ['Optional -> (Optional -> ())', 
+                                'var -> ()'])
         
 class TestUseDefChains(TestCase):
     def checkChains(self, code, ref):
diff --git a/tests/test_definitions.py b/tests/test_definitions.py
index e4702c7..e9f956a 100644
--- a/tests/test_definitions.py
+++ b/tests/test_definitions.py
@@ -37,11 +37,9 @@ class TestGlobals(TestCase):
         code = "def foo():\n def bar(): return"
         self.checkGlobals(code, ["foo"])
 
-    if sys.version_info.major >= 3:
-
-        def testAsyncFunctionDef(self):
-            code = "async def foo(): pass"
-            self.checkGlobals(code, ["foo"])
+    def testAsyncFunctionDef(self):
+        code = "async def foo(): pass"
+        self.checkGlobals(code, ["foo"])
 
     def testClassDef(self):
         code = "class C:pass"
@@ -75,11 +73,9 @@ class TestGlobals(TestCase):
         code = "x = 1"
         self.checkGlobals(code, ["x"])
 
-    if sys.version_info.major >= 3:
-
-        def testGlobalAnnotatedDef(self):
-            code = "x : 1"
-            self.checkGlobals(code, ["x"])
+    def testGlobalAnnotatedDef(self):
+        code = "x : 1"
+        self.checkGlobals(code, ["x"])
 
     def testMultipleGlobalDef(self):
         code = "x = 1; x = 2"
@@ -89,10 +85,9 @@ class TestGlobals(TestCase):
         code = "x, y = 1, 2"
         self.checkGlobals(code, ["x", "y"])
 
-    if sys.version_info.major >= 3:
-        def testGlobalStarredDestructuring(self):
-            code = "x, *y = 1, [2]"
-            self.checkGlobals(code, ["x", "y"])
+    def testGlobalStarredDestructuring(self):
+        code = "x, *y = 1, [2]"
+        self.checkGlobals(code, ["x", "y"])
 
     def testGlobalAugAssign(self):
         code = "x = 1; x += 2"
@@ -203,15 +198,13 @@ class TestGlobals(TestCase):
         code = "x = 1\ndef foo(): global x, y"
         self.checkGlobals(code, ["foo", "x"])
 
-    if sys.version_info.major >= 3:
-
-        def testGlobalAfterKeyword(self):
-            code = "def foo(): global x\nx : 1"
-            self.checkGlobals(code, ["foo", "x"])
+    def testGlobalAfterKeyword(self):
+        code = "def foo(): global x\nx : 1"
+        self.checkGlobals(code, ["foo", "x"])
 
-        def testGlobalsAfterKeyword(self):
-            code = "def foo(): global x, y\ny : 1"
-            self.checkGlobals(code, ["foo", "y"])
+    def testGlobalsAfterKeyword(self):
+        code = "def foo(): global x, y\ny : 1"
+        self.checkGlobals(code, ["foo", "y"])
 
     def testGlobalKeyworaInClassd(self):
         code = "class F: global x; x = 1"
@@ -267,31 +260,19 @@ class TestGlobals(TestCase):
 
     def testGlobalListComp(self):
         code = "from some import y; [1 for x in y]"
-        if sys.version_info.major == 2:
-            self.checkGlobals(code, ["x", "y"])
-        else:
-            self.checkGlobals(code, ["y"])
+        self.checkGlobals(code, ["y"])
 
     def testGlobalSetComp(self):
         code = "from some import y; {1 for x in y}"
-        if sys.version_info.major == 2:
-            self.checkGlobals(code, ["x", "y"])
-        else:
-            self.checkGlobals(code, ["y"])
+        self.checkGlobals(code, ["y"])
 
     def testGlobalDictComp(self):
         code = "from some import y; {1:1 for x in y}"
-        if sys.version_info.major == 2:
-            self.checkGlobals(code, ["x", "y"])
-        else:
-            self.checkGlobals(code, ["y"])
+        self.checkGlobals(code, ["y"])
 
     def testGlobalGeneratorExpr(self):
         code = "from some import y; (1 for x in y)"
-        if sys.version_info.major == 2:
-            self.checkGlobals(code, ["x", "y"])
-        else:
-            self.checkGlobals(code, ["y"])
+        self.checkGlobals(code, ["y"])
 
     def testGlobalLambda(self):
         code = "lambda x: x"
@@ -347,17 +328,9 @@ class TestLocals(TestCase):
         code = "def foo(a, **b): pass"
         self.checkLocals(code, ["a", "b"])
 
-    if sys.version_info.major >= 3:
-
-        def testLocalFunctionDefKwOnly(self):
-            code = "def foo(a, *, b=1): pass"
-            self.checkLocals(code, ["a", "b"])
-
-    if sys.version_info.major == 2:
-
-        def testLocalFunctionDefDestructureArg(self):
-            code = "def foo((a, b)): pass"
-            self.checkLocals(code, ["a", "b"])
+    def testLocalFunctionDefKwOnly(self):
+        code = "def foo(a, *, b=1): pass"
+        self.checkLocals(code, ["a", "b"])
 
     def test_LocalAssign(self):
         code = "def foo(): a = 1"
@@ -382,21 +355,19 @@ class TestLocals(TestCase):
         code = "def foo(a):\n def bar(): return a\n return bar"
         self.checkLocals(code, ["a", "bar"])
 
-    if sys.version_info.major >= 3:
-
-        def test_LocalNonLocalBefore(self):
-            code = "def foo(a):\n def bar():\n  nonlocal a; a = 1\n bar(); return a"
-            self.checkLocals(code, ["a", "bar"])
+    def test_LocalNonLocalBefore(self):
+        code = "def foo(a):\n def bar():\n  nonlocal a; a = 1\n bar(); return a"
+        self.checkLocals(code, ["a", "bar"])
 
-        def test_LocalNonLocalAfter(self):
-            code = (
-                "def foo():\n def bar():\n  nonlocal a; a = 1\n a = 2; bar(); return a"
-            )
-            self.checkLocals(code, ["a", "bar"])
+    def test_LocalNonLocalAfter(self):
+        code = (
+            "def foo():\n def bar():\n  nonlocal a; a = 1\n a = 2; bar(); return a"
+        )
+        self.checkLocals(code, ["a", "bar"])
 
-        def test_LocalDestructuring(self):
-            code = "def foo(x): y, *z = x"
-            self.checkLocals(code, ["x", "y", "z"])
+    def test_LocalDestructuring(self):
+        code = "def foo(x): y, *z = x"
+        self.checkLocals(code, ["x", "y", "z"])
 
     def test_LocalMadeGlobal(self):
         code = "def foo(): global a; a = 1"
@@ -404,10 +375,7 @@ class TestLocals(TestCase):
 
     def test_ListCompInLoop(self):
         code = "def foo(i):\n for j in i:\n  [k for k in j]"
-        if sys.version_info.major == 2:
-            self.checkLocals(code, ["i", "j", "k"])
-        else:
-            self.checkLocals(code, ["i", "j"])
+        self.checkLocals(code, ["i", "j"])
 
     def test_AugAssignInLoop(self):
         code = """
@@ -536,12 +504,8 @@ class TestDefIsLive(TestCase):
         x = True
         [x for x in (1,2)]
         '''
-        if sys.version_info > (3,):
-            node, c = self.checkLiveLocals(code, ['x:2'], ['x:2'])
-            self.checkLocals(c, node.body[-1].value, ['x:3'], only_live=True)
-        else:
-            self.checkLiveLocals(code, ['x:3'], ['x:2,3'])
-
+        node, c = self.checkLiveLocals(code, ['x:2'], ['x:2'])
+        self.checkLocals(c, node.body[-1].value, ['x:3'], only_live=True)
 
     def test_var_redef_in_method_scope(self):
         code = '''\
diff --git a/tests/test_ordereed_set.py b/tests/test_ordereed_set.py
index 3177349..23e8c59 100644
--- a/tests/test_ordereed_set.py
+++ b/tests/test_ordereed_set.py
@@ -1,101 +1,98 @@
-import sys
 from unittest import TestCase
 
-if sys.version_info.major >= 3:
-    
-    from beniget.ordered_set import ordered_set
+from beniget.ordered_set import ordered_set
 
-    class OrderedSetTest(TestCase):
+class OrderedSetTest(TestCase):
 
-        def test_add(self):
-            x = ordered_set([1, 2, -1, "bar"])
-            x.add(0)
-            assert list(x) == [1, 2, -1, "bar", 0]
+    def test_add(self):
+        x = ordered_set([1, 2, -1, "bar"])
+        x.add(0)
+        assert list(x) == [1, 2, -1, "bar", 0]
 
 
-        def test_discard(self):
-            x = ordered_set([1, 2, -1])
-            x.discard(2)
-            assert list(x) == [1, -1]
+    def test_discard(self):
+        x = ordered_set([1, 2, -1])
+        x.discard(2)
+        assert list(x) == [1, -1]
 
 
-        def test_discard_ignores_missing_element(self):
-            x = ordered_set()
-            x.discard(1)  # This does not raise
+    def test_discard_ignores_missing_element(self):
+        x = ordered_set()
+        x.discard(1)  # This does not raise
 
 
-        def test_remove(self):
-            x = ordered_set([1])
-            x.remove(1)
-            assert not x
+    def test_remove(self):
+        x = ordered_set([1])
+        x.remove(1)
+        assert not x
 
 
-        def test_remove_raises_missing_element(self):
-            x = ordered_set()
-            try:
-                x.remove(1)
-            except KeyError:
-                pass
-            else:
-                raise AssertionError('Should have raised IndexError')
+    def test_remove_raises_missing_element(self):
+        x = ordered_set()
+        try:
+            x.remove(1)
+        except KeyError:
+            pass
+        else:
+            raise AssertionError('Should have raised IndexError')
 
 
-        def test_getitem(self):
-            x = ordered_set([1, 2, -1])
-            assert x[0] == 1
-            assert x[1] == 2
-            assert x[2] == -1
-            try:
-                x[3]
-            except IndexError:
-                pass
-            else:
-                raise AssertionError('Should have raised IndexError')
+    def test_getitem(self):
+        x = ordered_set([1, 2, -1])
+        assert x[0] == 1
+        assert x[1] == 2
+        assert x[2] == -1
+        try:
+            x[3]
+        except IndexError:
+            pass
+        else:
+            raise AssertionError('Should have raised IndexError')
 
 
-        def test_len(self):
-            x = ordered_set([1])
-            assert len(x) == 1
+    def test_len(self):
+        x = ordered_set([1])
+        assert len(x) == 1
 
 
-        def test_iter(self):
-            for x in ordered_set([1]):
-                assert x == 1
+    def test_iter(self):
+        for x in ordered_set([1]):
+            assert x == 1
 
 
-        def test_str(self):
-            x = ordered_set([1, 2, 3])
-            assert str(x) == "{1, 2, 3}"
+    def test_str(self):
+        x = ordered_set([1, 2, 3])
+        assert str(x) == "{1, 2, 3}"
 
 
-        def test_repr(self):
-            x = ordered_set([1, 2, 3])
-            assert repr(x) == "<ordered_set {1, 2, 3}>"
+    def test_repr(self):
+        x = ordered_set([1, 2, 3])
+        assert repr(x) == "<ordered_set {1, 2, 3}>"
 
 
-        def test_eq(self):
-            x = ordered_set([1, 2, 3])
-            y = ordered_set([1, 2, 3])
-            assert x == y
-            assert x is not y
+    def test_eq(self):
+        x = ordered_set([1, 2, 3])
+        y = ordered_set([1, 2, 3])
+        assert x == y
+        assert x is not y
 
 
-        def test_init_empty(self):
-            x = ordered_set()
-            assert len(x) == 0
-            x.add(2)
-            assert len(x) == 1
+    def test_init_empty(self):
+        x = ordered_set()
+        assert len(x) == 0
+        x.add(2)
+        assert len(x) == 1
 
-        def test_dunder_add(self):
-            x = ordered_set([1, 2, 3])
-            y = ordered_set([1, 2, 3])
-            z = ordered_set([2, 4])
-            assert x+y == x
-            assert x+z == ordered_set([1, 2, 3, 4])
+    def test_dunder_add(self):
+        x = ordered_set([1, 2, 3])
+        y = ordered_set([1, 2, 3])
+        z = ordered_set([2, 4])
+        assert x+y == x
+        assert x+z == ordered_set([1, 2, 3, 4])
 
-        def test_update(self):
-            x = ordered_set([1, 2, 3])
-            x.update([1, 2, 3])
-            assert x == ordered_set([1, 2, 3])
-            x.update(ordered_set([1, 2, 3, 4]))
-            assert x == ordered_set([1, 2, 3, 4])
+    def test_update(self):
+        x = ordered_set([1, 2, 3])
+        x.update([1, 2, 3])
+        assert x == ordered_set([1, 2, 3])
+        x.update(ordered_set([1, 2, 3, 4]))
+        assert x == ordered_set([1, 2, 3, 4])