Commits

Victor Stinner committed 6630cc9

remove useless instructions

set also version to 0.5

  • Participants
  • Parent commits e84d435

Comments (0)

Files changed (6)

  * Remove dead code. Examples:
 
    - ``def f(): return 1; return 2`` => ``def f(): return 1``
+   - ``def f(a, b): s = a+b; 3; return s`` => ``def f(a, b): s = a+b; return s``
    - ``if DEBUG: print("debug")`` => ``pass`` with DEBUG declared as False
    - ``while 0: print("never executed")`` => ``pass``
 
 ChangeLog
 =========
 
+Version 0.5
+-----------
+
+Changes:
+
+ * Remove useless instructions. Example:
+   "x=1; 'abc'; print(x)" => "x=1; print(x)"
+
 Version 0.4 (2012-12-10)
 ------------------------
 
    * "x=[0]; for ...: x.append(...)"
      => "x=[0]; x_append=x.append; for ...: x_append(...)"
 
- - drop dead code with a warning: "x=1; 'useless instruction'; print(x)"
+ - constant folding:
 
    * "x=1; print(x)" => "print('1')" (drop x)
-   * "x=1" drops x
-   * "return x" keeps x
+   * "x=1" => "pass" (drop x)
+   * "return x" => "return x" (keep x)
 
- - drop unused instructions with a warning: "def f(): x=1; return 2"
+ - drop unused variables with a warning:
+
+   * "def f(): x=1; return 2"
+
  - support variables: x="abc"; print(len(x))
 
    * rewrite the optimizer to work in two steps:

File astoptimizer/config.py

         # Example: "if 0: print('debug')" => "pass"
         self.remove_dead_code = True
 
+        # Remove dead code in a module body?
+        # Option used by astoptimizer unit tests
+        # Example: "print('hello'); 1" => "print('hello')"
+        self._remove_module_dead_code = True
+
+        # Remove documentation strings?
+        # False by default, True if the option -O is specified
+        # in the command line
+        self.remove_docstring = sys.flags.optimize
+
         # Callback to display a information message, prototype:
         #
         # def info(message):

File astoptimizer/optimizer.py

         self.optimize_node_list(new_node_list)
         return new_node_list
 
+    def _remove_node(self, index, node):
+        if not isinstance(node, ast.Expr):
+            return False
+        expr = node.value
+        if not isinstance(expr, (ast.Str, ast.Num)):
+            return False
+        if (index == 0
+        and isinstance(expr, ast.Str)
+        and not self.config.remove_docstring):
+            return False
+        return True
+
+    def visit_expr_list(self, node_list, conditional=False):
+        if self.config.remove_dead_code:
+            new_node_list = []
+            for index, node in enumerate(node_list):
+                if self._remove_node(index, node):
+                    self.log_node_removal(node)
+                else:
+                    new_node_list.append(node)
+            if not new_node_list:
+                pass_node = ast.Pass()
+                copy_lineno(node_list[0], pass_node)
+                new_node_list.append(pass_node)
+        else:
+            new_node_list = node_list
+
+        return self.visit_list(new_node_list, conditional)
+
     def visit_Name(self, node):
         constant = self.load_name(node.id)
         if constant is UNSET:
             return
         return new_constant(node, constant)
 
+    def fullvisit_Module(self, node):
+        if self.config._remove_module_dead_code:
+            node.body = self.visit_expr_list(node.body)
+        else:
+            node.body = self.visit_list(node.body)
+
     def get_attribute_name(self, node):
         if isinstance(node.value, ast.Name):
             name = node.value.id
 
     def fullvisit_FunctionDef(self, node):
         if node is self.root:
-            return self.generic_visit(node)
+            node.args = self.visit(node.args)
+            node.body = self.visit_expr_list(node.body)
+            return node
         else:
             return Optimizer.fullvisit_FunctionDef(self, node)
 

File astoptimizer/tests.py

         self._default_config = self.create_default_config()
 
     def tearDown(self):
-        if self.warnings:
-            raise Exception("WARNINGS: %s" % self.warnings)
+        self._check_infos(None)
+        self._check_warnings(None)
 
     def log_info(self, message):
         self.infos.append(message)
         config = Config(*features)
         config.info_func = self.log_info
         config.warning_func = self.log_warning
+        config._remove_module_dead_code = False
         return config
 
     def create_default_config(self):
                 raise
         return tree, optimizer
 
-    def _check_messages(self, messages, message):
+    def _check_messages(self, what, messages, message):
         emitted = messages[:]
         del messages[:]
         if message:
                     # regex
                     self.assertTrue(message.match(emitted), emitted)
         elif emitted:
-            raise Exception("WARNINGS: %s" % emitted)
+            raise Exception("%s: %s" % (what, emitted))
 
     def _check_infos(self, message):
-        self._check_messages(self.infos, message)
+        self._check_messages("INFOS", self.infos, message)
 
     def _check_warnings(self, message):
-        self._check_messages(self.warnings, message)
+        self._check_messages("WARNINGS", self.warnings, message)
 
     def check(self, code, expected, config=None, info=None, warning=None):
         tree = parse_ast(code)
             self.check_not_optimized('%r[3::-1]' % u("abc\U0010ffff-"))
             self.check_not_optimized('%r[3:0:-1]' % u("abc\U0010ffff-"))
 
-            config = Config("pythonbin")
+            config = self.create_config("pythonbin")
             self.check('%r[0]' % u("\U0010ffff"), self.text_unicode(u('\U0010ffff')[0]), config)
             self.check('%r[2]' % u("abc\U0010ffff-"), self.text_unicode(u('c')), config)
         else:
         """.strip()
         self.check_not_optimized(code, catch_syntaxerror=True)
 
+        # docstring
+        keep_docstring = self.create_config()
+        keep_docstring.remove_docstring = False
+        self.check('def f():\n "docstring"\n return 1',
+                   self.text_ast('def f():\n "docstring"\n return 1'),
+                   config=keep_docstring)
+
+        no_docstring = self.create_config()
+        no_docstring.remove_docstring = True
+        self.check('def f():\n "docstring"\n return 1',
+                   self.text_ast('def f():\n return 1'),
+                   info="Remove dead code at <string>:2: Expr(value=Str(s='docstring'))",
+                   config=no_docstring)
+
     def test_dont_remove_dead_code(self):
         # disable removal of dead code
         config = Config()
             after = 'if a and b:\n print("true")'
         if 'return 1; return 2' in before:
             info = 'Remove dead code at <string>:1: Return(value=Num(n=2))'
+        if ' 3; return s' in before:
+            info = 'Remove dead code at <string>:1: Expr(value=Num(n=3))'
 
         if 'if DEBUG:' in before:
             config = self.create_default_config()

File astoptimizer/version.py

 PACKAGE = "astoptimizer"
-VERSION = "0.4"
+VERSION = "0.5"
 WEBSITE = "https://bitbucket.org/haypo/astoptimizer"
 LICENSE = "BSD (2 clauses)"