From 7c4b83ed0e799539497eb49e8331a92486efd96b Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Thu, 2 Jul 2026 17:19:07 +0200 Subject: [PATCH] Track namespace and getattr aliases --- .../test_generate_popular_node_signatures.py | 100 ++++++++++++++++++ tools/generate_popular_node_signatures.py | 36 ++++--- 2 files changed, 124 insertions(+), 12 deletions(-) diff --git a/tests/test_generate_popular_node_signatures.py b/tests/test_generate_popular_node_signatures.py index b4c49fb..8dc2a67 100644 --- a/tests/test_generate_popular_node_signatures.py +++ b/tests/test_generate_popular_node_signatures.py @@ -2303,6 +2303,56 @@ globals().get("GlobalsGetReturnTypesNode").RETURN_TYPES.clear() self.assertEqual({}, result["nodes"]) self.assertEqual("no_static_nodes", result["pack"]["status"]) + def test_getattr_class_attribute_alias_mutation_after_mapping_skips_node(self): + source = ''' +class GetattrAttributeAliasNode: + RETURN_TYPES = ["IMAGE"] + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + }, + } + + +NODE_CLASS_MAPPINGS = { + "GetattrAttributeAliasNode": GetattrAttributeAliasNode, +} +RET = getattr(GetattrAttributeAliasNode, "RETURN_TYPES") +RET.clear() +''' + result = self._extract_source(source, "getattr-attribute-alias-pack") + + self.assertEqual({}, result["nodes"]) + self.assertEqual("no_static_nodes", result["pack"]["status"]) + + def test_getattr_namespace_class_attribute_alias_mutation_after_mapping_skips_node(self): + source = ''' +class GetattrNamespaceAttributeAliasNode: + RETURN_TYPES = ["IMAGE"] + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + }, + } + + +NODE_CLASS_MAPPINGS = { + "GetattrNamespaceAttributeAliasNode": GetattrNamespaceAttributeAliasNode, +} +RET = getattr(globals()["GetattrNamespaceAttributeAliasNode"], "RETURN_TYPES") +RET.clear() +''' + result = self._extract_source(source, "getattr-namespace-attribute-alias-pack") + + self.assertEqual({}, result["nodes"]) + self.assertEqual("no_static_nodes", result["pack"]["status"]) + def test_module_class_tuple_alias_patch_after_mapping_skips_node(self): source = ''' class TupleAliasPatchedNode: @@ -2451,6 +2501,56 @@ globals().update(NODE_CLASS_MAPPINGS={}) self.assertEqual({}, result["nodes"]) self.assertEqual("no_static_nodes", result["pack"]["status"]) + def test_globals_subscript_alias_mutation_invalidates_static_node_mapping(self): + source = ''' +class GlobalAliasMutatedMappingNode: + RETURN_TYPES = ("IMAGE",) + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + }, + } + + +NODE_CLASS_MAPPINGS = { + "GlobalAliasMutatedMappingNode": GlobalAliasMutatedMappingNode, +} +ALIAS = globals()["NODE_CLASS_MAPPINGS"] +ALIAS.clear() +''' + result = self._extract_source(source, "global-alias-mutated-mapping-pack") + + self.assertEqual({}, result["nodes"]) + self.assertEqual("no_static_nodes", result["pack"]["status"]) + + def test_globals_get_alias_mutation_invalidates_static_node_mapping(self): + source = ''' +class GlobalGetAliasMutatedMappingNode: + RETURN_TYPES = ("IMAGE",) + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + }, + } + + +NODE_CLASS_MAPPINGS = { + "GlobalGetAliasMutatedMappingNode": GlobalGetAliasMutatedMappingNode, +} +ALIAS = globals().get("NODE_CLASS_MAPPINGS") +ALIAS.clear() +''' + result = self._extract_source(source, "global-get-alias-mutated-mapping-pack") + + self.assertEqual({}, result["nodes"]) + self.assertEqual("no_static_nodes", result["pack"]["status"]) + def test_unpacked_alias_mutation_invalidates_static_node_mapping(self): source = ''' class UnpackedAliasMutatedMappingNode: diff --git a/tools/generate_popular_node_signatures.py b/tools/generate_popular_node_signatures.py index d479296..ca8178a 100644 --- a/tools/generate_popular_node_signatures.py +++ b/tools/generate_popular_node_signatures.py @@ -1035,14 +1035,22 @@ def _expanded_class_attribute_names(names, class_aliases): def _class_attribute_alias_sources(value, class_attribute_aliases, class_aliases, class_bindings): if isinstance(value, ast.Name): return set(class_attribute_aliases.get(value.id, ())) - if not isinstance(value, ast.Attribute) or value.attr not in _CLASS_SIGNATURE_ATTRS: - return set() - name = _root_name(value.value) - if name in class_aliases: - return set(class_aliases[name]) - if name in class_bindings: - return {name} - return set() + + names = set() + if isinstance(value, ast.Attribute) and value.attr in _CLASS_SIGNATURE_ATTRS: + name = _root_name(value.value) + if name is not None: + names.add(name) + else: + names.update(_getattr_signature_target_names(value)) + + sources = set() + for name in names: + if name in class_aliases: + sources.update(class_aliases[name]) + if name in class_bindings: + sources.add(name) + return sources def _update_class_attribute_alias_from_unpack( @@ -1125,11 +1133,15 @@ def _module_class_attribute_invalidated_names(stmt, class_aliases, class_attribu def _module_dict_alias_sources(value, name, aliases): - if not isinstance(value, ast.Name): - return set() - if value.id == name: + if isinstance(value, ast.Name): + if value.id == name: + return {name} + return set(aliases.get(value.id, ())) + + namespace_name = _namespace_subscript_name(value) or _namespace_lookup_name(value) + if namespace_name == name: return {name} - return set(aliases.get(value.id, ())) + return set() def _update_module_dict_alias_from_unpack(target, value, name, aliases):