Skip to content

Commit

Permalink
Add early transforms before handling delayed assattr nodes
Browse files Browse the repository at this point in the history
When importing Gtk, it looks like this:

    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk

It is vital that gi.require_version() is made before related 'from
gi.repository import ...'. The brain_gi tries to do that using
transforms. And it works unless Gtk is imported as part of delayed
assattr handling.

Fix this by adding early transforms that are called before delayed
assattr.

Fixes #2190
Fixes pylint-dev/pylint#6352
  • Loading branch information
marmarek committed Jun 27, 2024
1 parent 453d307 commit 9518cd4
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 1 deletion.
2 changes: 1 addition & 1 deletion astroid/brain/brain_gi.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,6 @@ def _register_require_version(node):

def register(manager: AstroidManager) -> None:
manager.register_failed_import_hook(_import_gi_module)
manager.register_transform(
manager.register_early_transform(
nodes.Call, _register_require_version, _looks_like_require_version
)
5 changes: 5 additions & 0 deletions astroid/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def module_build(
if self._apply_transforms:
# We have to handle transformation by ourselves since the
# rebuilder isn't called for builtin nodes
node = self._manager.visit_early_transforms(node)
node = self._manager.visit_transforms(node)
assert isinstance(node, nodes.Module)
return node
Expand Down Expand Up @@ -164,6 +165,10 @@ def _post_build(
for symbol, _ in from_node.names:
module.future_imports.add(symbol)
self.add_from_names_to_locals(from_node)
# Visit the transforms
if self._apply_transforms:
module = self._manager.visit_early_transforms(module)

# handle delayed assattr nodes
for delayed in builder._delayed_assattr:
self.delayed_assattr(delayed)
Expand Down
18 changes: 18 additions & 0 deletions astroid/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class AstroidManager:
"extension_package_whitelist": set(),
"module_denylist": set(),
"_transform": TransformVisitor(),
"_early_transform": TransformVisitor(),
"prefer_stubs": False,
}

Expand All @@ -75,6 +76,7 @@ def __init__(self) -> None:
]
self.module_denylist = AstroidManager.brain["module_denylist"]
self._transform = AstroidManager.brain["_transform"]
self._early_transform = AstroidManager.brain["_early_transform"]
self.prefer_stubs = AstroidManager.brain["prefer_stubs"]

@property
Expand Down Expand Up @@ -110,6 +112,15 @@ def register_transform(self):
def unregister_transform(self):
return self._transform.unregister_transform

@property
def register_early_transform(self):
# This and unregister_early_transform below are exported for convenience
return self._early_transform.register_transform

@property
def unregister_early_transform(self):
return self._early_transform.unregister_transform

@property
def builtins_module(self) -> nodes.Module:
return self.astroid_cache["builtins"]
Expand All @@ -126,6 +137,10 @@ def visit_transforms(self, node: nodes.NodeNG) -> InferenceResult:
"""Visit the transforms and apply them to the given *node*."""
return self._transform.visit(node)

def visit_early_transforms(self, node: nodes.NodeNG) -> InferenceResult:
"""Visit the early transforms and apply them to the given *node*."""
return self._early_transform.visit(node)

def ast_from_file(
self,
filepath: str,
Expand Down Expand Up @@ -466,6 +481,9 @@ def clear_cache(self) -> None:
self.astroid_cache.clear()
# NB: not a new TransformVisitor()
AstroidManager.brain["_transform"].transforms = collections.defaultdict(list)
AstroidManager.brain["_early_transform"].transforms = collections.defaultdict(
list
)

for lru_cache in (
LookupMixIn.lookup,
Expand Down
1 change: 1 addition & 0 deletions astroid/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def brainless_manager():
m.astroid_cache = {}
m._mod_file_cache = {}
m._transform = transforms.TransformVisitor()
m._early_transform = transforms.TransformVisitor()
m.extension_package_whitelist = set()
m.module_denylist = set()
return m
16 changes: 16 additions & 0 deletions tests/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,22 @@ def transform_time(node: Module) -> None:
finally:
self.manager.unregister_transform(nodes.Module, transform_time)

def test_inspect_early_transform_module(self) -> None:
# ensure no cached version of the time module
self.manager._mod_file_cache.pop(("time", None), None)
self.manager.astroid_cache.pop("time", None)

def transform_time(node: Module) -> None:
if node.name == "time":
node.transformed = True

self.manager.register_early_transform(nodes.Module, transform_time)
try:
time_ast = self.manager.ast_from_module_name("time")
self.assertTrue(getattr(time_ast, "transformed", False))
finally:
self.manager.unregister_early_transform(nodes.Module, transform_time)

def test_package_name(self) -> None:
"""Test base properties and method of an astroid module."""
datap = resources.build_file("data/__init__.py", "data")
Expand Down

0 comments on commit 9518cd4

Please sign in to comment.