Repository: cython/cython

Issue:
# cython: language_level=2 does not work as expected
There seems to be a problem with handling `# cython: language_level=2` when `language_level=3str` is set globally (for example, using the `compiler_directives` flag of `cythonize()`).

For some features like division, the Python 3 semantics are used anyway.

Example:
```
# cython: language_level=2
def test():
    ver = 3
    L = [ver for ver in [2]]
    print(f"effective language_level for list comprehension: {ver}")

    cdef object a = 3
    ver = int((a / 2) * 2)
    print(f"effective language_level for division: {ver}")
```
gives as output
```
>>> test()
effective language_level for list comprehension: 2
effective language_level for division: 3
```

Strangely enough, the opposite (`# cython: language_level=3str` when `language_level=2` is set globally) works correctly.


Discussion:
The problems seems to be that `set_language_level` only *adds* `future_directives` but it never removes any.
PR coming...

Resolving patch:
diff --git a/Cython/Compiler/Main.py b/Cython/Compiler/Main.py
--- a/Cython/Compiler/Main.py
+++ b/Cython/Compiler/Main.py
@@ -99,18 +99,17 @@ def __init__(self, include_directories, compiler_directives, cpp=False,
 
     def set_language_level(self, level):
         from .Future import print_function, unicode_literals, absolute_import, division, generator_stop
-        future_directives = []
+        future_directives = set()
         if level == '3str':
-            future_directives = [print_function, absolute_import, division, generator_stop]
-            self.future_directives.discard(unicode_literals)
             level = 3
         else:
             level = int(level)
             if level >= 3:
-                future_directives = [print_function, unicode_literals, absolute_import, division, generator_stop]
+                future_directives.add(unicode_literals)
+        if level >= 3:
+            future_directives.update([print_function, absolute_import, division, generator_stop])
         self.language_level = level
-        if future_directives:
-            self.future_directives.update(future_directives)
+        self.future_directives = future_directives
         if level >= 3:
             self.modules['builtins'] = self.modules['__builtin__']
 


Test patch:
diff --git a/tests/run/language_level.srctree b/tests/run/language_level.srctree
--- a/tests/run/language_level.srctree
+++ b/tests/run/language_level.srctree
@@ -7,12 +7,19 @@ PYTHON -c "import infile2; import infile3"
 from Cython.Build.Dependencies import cythonize
 from distutils.core import setup
 
-setup(
-    ext_modules = (cythonize("infile*.py") +
-                   cythonize("directive2.py", compiler_directives={'language_level': 2}) +
-                   cythonize("directive3.py", compiler_directives={'language_level': 3})
-                   )
-)
+ext_modules = []
+
+# Test language_level specified in the cythonize() call
+ext_modules += cythonize("directive2.py", compiler_directives={'language_level': 2})
+ext_modules += cythonize("directive3.py", compiler_directives={'language_level': 3})
+
+# Test language_level specified in the source file. We give a
+# conflicting directive to cythonize() to check that the language_level
+# is correctly overridden when compiling
+ext_modules += cythonize("infile2.py", compiler_directives={'language_level': 3})
+ext_modules += cythonize("infile3.py", compiler_directives={'language_level': 2})
+
+setup(ext_modules=ext_modules)
 
 ######## directive3.py ########