From 99d2bb25da75f1f1d9a9d6781be32164ff74e0ef Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Thu, 2 Jul 2026 14:09:07 +0200 Subject: [PATCH] Fail closed on mutable module captures in class signatures --- .../test_generate_popular_node_signatures.py | 57 +++++++++++++++++++ tools/generate_popular_node_signatures.py | 22 +++++-- 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/tests/test_generate_popular_node_signatures.py b/tests/test_generate_popular_node_signatures.py index 9c48592..a7abe41 100644 --- a/tests/test_generate_popular_node_signatures.py +++ b/tests/test_generate_popular_node_signatures.py @@ -855,6 +855,63 @@ NODE_CLASS_MAPPINGS = { self.assertEqual(["IMAGE"], result["nodes"]["DefinitionTimeReturnTypesNode"]["outputs"]) self.assertEqual("ok", result["pack"]["status"]) + def test_mutable_module_return_types_capture_skips_node(self): + source = ''' +RETURNS = ["IMAGE"] + + +class MutableModuleReturnTypesNode: + RETURN_TYPES = RETURNS + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + }, + } + + +RETURNS.clear() + +NODE_CLASS_MAPPINGS = { + "MutableModuleReturnTypesNode": MutableModuleReturnTypesNode, +} +''' + result = self._extract_source(source, "mutable-module-return-types-pack") + + self.assertEqual({}, result["nodes"]) + self.assertEqual("no_static_nodes", result["pack"]["status"]) + + def test_mutable_module_return_names_capture_skips_node(self): + source = ''' +NAMES = ["image"] + + +class MutableModuleReturnNamesNode: + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = NAMES + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + }, + } + + +NAMES.clear() + +NODE_CLASS_MAPPINGS = { + "MutableModuleReturnNamesNode": MutableModuleReturnNamesNode, +} +''' + result = self._extract_source(source, "mutable-module-return-names-pack") + + self.assertEqual({}, result["nodes"]) + self.assertEqual("no_static_nodes", result["pack"]["status"]) + def test_subscript_assignment_to_return_types_skips_node(self): source = ''' class SubscriptMutatedReturnTypesNode: diff --git a/tools/generate_popular_node_signatures.py b/tools/generate_popular_node_signatures.py index 01d4b60..9583e74 100644 --- a/tools/generate_popular_node_signatures.py +++ b/tools/generate_popular_node_signatures.py @@ -398,6 +398,10 @@ def _class_defs(tree): return {node.name: node for node in tree.body if isinstance(node, ast.ClassDef)} +def _is_mutable_env_reference(node, env): + return isinstance(node, ast.Name) and node.id in env and _is_mutable_static_value(env[node.id]) + + def _class_attr(cls, name, env): value = _MISSING aliases = set() @@ -419,10 +423,13 @@ def _class_attr(cls, name, env): if name not in target_names: continue if len(stmt.targets) == 1 and isinstance(stmt.targets[0], ast.Name): - try: - value = _literal(stmt.value, env) - except UnsupportedStaticExpression: + if _is_mutable_env_reference(stmt.value, env): value = _INVALID + else: + try: + value = _literal(stmt.value, env) + except UnsupportedStaticExpression: + value = _INVALID else: value = _INVALID continue @@ -446,10 +453,13 @@ def _class_attr(cls, name, env): if not isinstance(stmt.target, ast.Name): value = _INVALID else: - try: - value = _literal(stmt.value, env) - except UnsupportedStaticExpression: + if _is_mutable_env_reference(stmt.value, env): value = _INVALID + else: + try: + value = _literal(stmt.value, env) + except UnsupportedStaticExpression: + value = _INVALID continue if isinstance(stmt, ast.AugAssign): target_names = _assignment_target_names(stmt)