diff --git a/src/python_minifier/__init__.py b/src/python_minifier/__init__.py index 9863b791..a7ab2b67 100644 --- a/src/python_minifier/__init__.py +++ b/src/python_minifier/__init__.py @@ -166,6 +166,18 @@ def minify( rename_globals = False rename_locals = False + if preserve_locals is None: + preserve_locals = [] + elif isinstance(preserve_locals, str): + preserve_locals = [preserve_locals] + if preserve_globals is None: + preserve_globals = [] + elif isinstance(preserve_globals, str): + preserve_globals = [preserve_globals] + + preserve_locals.extend(module.preserved) + preserve_globals.extend(module.preserved) + allow_rename_locals(module, rename_locals, preserve_locals) allow_rename_globals(module, rename_globals, preserve_globals) diff --git a/src/python_minifier/rename/bind_names.py b/src/python_minifier/rename/bind_names.py index 1a2f586a..57597674 100644 --- a/src/python_minifier/rename/bind_names.py +++ b/src/python_minifier/rename/bind_names.py @@ -15,6 +15,7 @@ class NameBinder(NodeVisitor): def __call__(self, module): assert isinstance(module, ast.Module) module.tainted = False + module.preserved = set() return self.visit(module) def get_binding(self, name, namespace): @@ -161,6 +162,24 @@ def visit_MatchMapping(self, node): self.generic_visit(node) + def visit_TypeVar(self, node): + if node.name not in node.namespace.nonlocal_names: + self.get_binding(node.name, node.namespace).add_reference(node) + + get_global_namespace(node.namespace).preserved.add(node.name) + + def visit_TypeVarTuple(self, node): + if node.name not in node.namespace.nonlocal_names: + self.get_binding(node.name, node.namespace).add_reference(node) + + get_global_namespace(node.namespace).preserved.add(node.name) + + def visit_ParamSpec(self, node): + if node.name not in node.namespace.nonlocal_names: + self.get_binding(node.name, node.namespace).add_reference(node) + + get_global_namespace(node.namespace).preserved.add(node.name) + def bind_names(module): """ Bind names to their local namespace diff --git a/src/python_minifier/rename/binding.py b/src/python_minifier/rename/binding.py index d18f1703..9f58058b 100644 --- a/src/python_minifier/rename/binding.py +++ b/src/python_minifier/rename/binding.py @@ -130,6 +130,12 @@ def additional_byte_cost(self): pass elif is_ast_node(node, 'MatchMapping'): pass + elif is_ast_node(node, 'TypeVar'): + pass + elif is_ast_node(node, 'TypeVarTuple'): + pass + elif is_ast_node(node, 'ParamSpec'): + pass else: raise AssertionError('Unknown reference node') @@ -177,6 +183,12 @@ def old_mention_count(self): pass elif is_ast_node(node, 'MatchMapping'): pass + elif is_ast_node(node, 'TypeVar'): + pass + elif is_ast_node(node, 'TypeVarTuple'): + pass + elif is_ast_node(node, 'ParamSpec'): + pass else: raise AssertionError('Unknown reference node') @@ -220,6 +232,12 @@ def new_mention_count(self): mentions += 1 elif is_ast_node(node, 'MatchMapping'): mentions += 1 + elif is_ast_node(node, 'TypeVar'): + mentions += 1 + elif is_ast_node(node, 'TypeVarTuple'): + mentions += 1 + elif is_ast_node(node, 'ParamSpec'): + mentions += 1 else: raise AssertionError('Unknown reference node') @@ -386,6 +404,12 @@ def rename(self, new_name): node.name = new_name elif is_ast_node(node, 'MatchMapping'): node.rest = new_name + elif is_ast_node(node, 'TypeVar'): + node.name = new_name + elif is_ast_node(node, 'TypeVarTuple'): + node.name = new_name + elif is_ast_node(node, 'ParamSpec'): + node.name = new_name if func_namespace_binding is not None: func_namespace_binding.body = list( diff --git a/xtest/manifests/python3.10_test_manifest.yaml b/xtest/manifests/python3.10_test_manifest.yaml index e958d54d..5603f85c 100644 --- a/xtest/manifests/python3.10_test_manifest.yaml +++ b/xtest/manifests/python3.10_test_manifest.yaml @@ -865,17 +865,21 @@ rename_globals: true /usr/local/lib/python3.10/test/test_contextlib_async.py: - options: - preserve_locals: baz + preserve_locals: + - baz - options: rename_globals: true - preserve_locals: baz + preserve_locals: + - baz - options: remove_literal_statements: true - preserve_locals: baz + preserve_locals: + - baz - options: remove_literal_statements: true rename_globals: true - preserve_locals: baz + preserve_locals: + - baz /usr/local/lib/python3.10/test/test_copy.py: - options: {} - options: diff --git a/xtest/manifests/python3.11_test_manifest.yaml b/xtest/manifests/python3.11_test_manifest.yaml index 74384540..b8158cc2 100644 --- a/xtest/manifests/python3.11_test_manifest.yaml +++ b/xtest/manifests/python3.11_test_manifest.yaml @@ -188,17 +188,21 @@ /usr/local/lib/python3.11/test/test_asyncio/test_futures.py: [] /usr/local/lib/python3.11/test/test_asyncio/test_futures2.py: - options: - preserve_locals: future + preserve_locals: + - future - options: rename_globals: true - preserve_locals: future + preserve_locals: + - future - options: remove_literal_statements: true - preserve_locals: future + preserve_locals: + - future - options: remove_literal_statements: true rename_globals: true - preserve_locals: future + preserve_locals: + - future /usr/local/lib/python3.11/test/test_asyncio/test_locks.py: - options: {} - options: @@ -2904,17 +2908,21 @@ rename_globals: true /usr/local/lib/python3.11/test/test_opcache.py: - options: - preserve_locals: Class + preserve_locals: + - Class - options: rename_globals: true - preserve_locals: Class + preserve_locals: + - Class - options: remove_literal_statements: true - preserve_locals: Class + preserve_locals: + - Class - options: remove_literal_statements: true rename_globals: true - preserve_locals: Class + preserve_locals: + - Class /usr/local/lib/python3.11/test/test_opcodes.py: - options: {} - options: diff --git a/xtest/manifests/python3.12_test_manifest.yaml b/xtest/manifests/python3.12_test_manifest.yaml index 4c143483..c1a6a4a4 100644 --- a/xtest/manifests/python3.12_test_manifest.yaml +++ b/xtest/manifests/python3.12_test_manifest.yaml @@ -4740,20 +4740,24 @@ status: passing /usr/local/lib/python3.12/test/test_opcache.py: - options: - preserve_locals: Class + preserve_locals: + - Class remove_literal_statements: true rename_globals: true status: passing - options: - preserve_locals: Class + preserve_locals: + - Class rename_globals: true status: passing - options: - preserve_locals: Class + preserve_locals: + - Class remove_literal_statements: true status: passing - options: - preserve_locals: Class + preserve_locals: + - Class status: passing /usr/local/lib/python3.12/test/test_opcodes.py: - options: