bpo-39313: Add an option to RefactoringTool for using exec as a function (GH-17967)



https://bugs.python.org/issue39313


Automerge-Triggered-By: @pablogsal
diff --git a/Lib/lib2to3/main.py b/Lib/lib2to3/main.py
index c51626b..f2849fd 100644
--- a/Lib/lib2to3/main.py
+++ b/Lib/lib2to3/main.py
@@ -154,6 +154,8 @@
                       help="List available transformations")
     parser.add_option("-p", "--print-function", action="store_true",
                       help="Modify the grammar so that print() is a function")
+    parser.add_option("-e", "--exec-function", action="store_true",
+                      help="Modify the grammar so that exec() is a function")
     parser.add_option("-v", "--verbose", action="store_true",
                       help="More verbose logging")
     parser.add_option("--no-diffs", action="store_true",
@@ -211,6 +213,9 @@
     if options.print_function:
         flags["print_function"] = True
 
+    if options.exec_function:
+        flags["exec_function"] = True
+
     # Set up logging handler
     level = logging.DEBUG if options.verbose else logging.INFO
     logging.basicConfig(format='%(name)s: %(message)s', level=level)
diff --git a/Lib/lib2to3/refactor.py b/Lib/lib2to3/refactor.py
index 55fd60f..3a5aaff 100644
--- a/Lib/lib2to3/refactor.py
+++ b/Lib/lib2to3/refactor.py
@@ -155,6 +155,7 @@
 class RefactoringTool(object):
 
     _default_options = {"print_function" : False,
+                        "exec_function": False,
                         "write_unchanged_files" : False}
 
     CLASS_PREFIX = "Fix" # The prefix for fixer classes
@@ -173,10 +174,13 @@
         self.options = self._default_options.copy()
         if options is not None:
             self.options.update(options)
-        if self.options["print_function"]:
-            self.grammar = pygram.python_grammar_no_print_statement
-        else:
-            self.grammar = pygram.python_grammar
+        self.grammar = pygram.python_grammar.copy()
+
+        if self.options['print_function']:
+            del self.grammar.keywords["print"]
+        elif self.options['exec_function']:
+            del self.grammar.keywords["exec"]
+
         # When this is True, the refactor*() methods will call write_file() for
         # files processed even if they were not changed during refactoring. If
         # and only if the refactor method's write parameter was True.
diff --git a/Lib/lib2to3/tests/test_refactor.py b/Lib/lib2to3/tests/test_refactor.py
index 9e3b8fb..be70567 100644
--- a/Lib/lib2to3/tests/test_refactor.py
+++ b/Lib/lib2to3/tests/test_refactor.py
@@ -44,9 +44,13 @@
 
     def test_print_function_option(self):
         rt = self.rt({"print_function" : True})
-        self.assertIs(rt.grammar, pygram.python_grammar_no_print_statement)
-        self.assertIs(rt.driver.grammar,
-                      pygram.python_grammar_no_print_statement)
+        self.assertNotIn("print", rt.grammar.keywords)
+        self.assertNotIn("print", rt.driver.grammar.keywords)
+
+    def test_exec_function_option(self):
+        rt = self.rt({"exec_function" : True})
+        self.assertNotIn("exec", rt.grammar.keywords)
+        self.assertNotIn("exec", rt.driver.grammar.keywords)
 
     def test_write_unchanged_files_option(self):
         rt = self.rt()