-
Notifications
You must be signed in to change notification settings - Fork 161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PR: eliminate call_for_nodes #633
Conversation
I don't know what makes you think that It's implemented as recursive function i.e. using implicit stack, and it has a mechanism for early exit once it found a match. |
It's basically an AST-based code search mechanism, where Rope constructs a tree of ast nodes to match and
The output of patchedast.py itself (i.e. sorted_children) is used by other refactoring code, mainly extract and restructuring refactoring.
|
@lieryan > call_for_nodes is just a simple, textbook pre-order DFS/traversal This would be the textbook traversal: def traverse(node, callback):
callback(node)
for child in ast.iter_child_nodes(node):
traverse(child, callback)
def call_for_nodes(node, callback):
result = callback(node)
if not result:
for child in ast.iter_child_nodes(node):
call_for_nodes(child, callback)
If more than one child returns a result, what happens depends on how the caller uses Rope uses _ASTMatcher.find_matches in similarfinder.py def find_matches(self):
if self.matches is None:
self.matches = []
ast.call_for_nodes(self.body, self._check_node)
return self.matches Only
Anyway, Perhaps the code works (or almost works?) because the partial traversal happens when comparing two trees which are always (usually?) similar enough that the partial traversals match. But this is hand waving. I'm totally confused. _ResultChecker._find_node in patchedasttest.py def _find_node(self, text):
goal = text
if not isinstance(text, (tuple, list)):
goal = [text]
<< define class Search >>
search = Search()
ast.call_for_nodes(self.ast, search)
return search.result
class Search:
result = None
def __call__(self, node):
for text in goal:
if sys.version_info >= (3, 8) and text in [
"Num",
"Str",
"NameConstant",
"Ellipsis",
]:
text = "Constant"
if str(node).startswith(text):
self.result = node
break
if node.__class__.__name__.startswith(text):
self.result = node
break
return self.result is not None Once again, the incomplete nature of But Aha. Using this Aha, perhaps def _find_node(self, text):
goal = text
if not isinstance(text, (tuple, list)):
goal = [text]
def match(node):
for text in goal:
if sys.version_info >= (3, 8) and text in [
"Num", "Str", "NameConstant", "Ellipsis",
]:
text = "Constant"
if str(node).startswith(text):
raise Found(node)
if node.__class__.__name__.startswith(text):
raise Found(node)
def find(node):
match(node)
for child in ast.iter_child_nodes(node):
find(child)
try:
find(self.ast)
return None
except Found as node:
return node Summary I don't understand why I think I do understand why |
@lieryan I am going to mark this as a draft while I play with simplifying |
Think of it like searching in a regular string:
Once you found the location of "def" inside
Strictly speaking, For the purpose extract refactoring and restructuring, they don't really have a well-defined meaning if the matched call nodes internally have additional matches. For similar finder, the current implementation only returns the outer-most match, you could potentially generate additional matches if the matched nodes have grandchildren that also matches the search pattern, but it may not necessarily be desirable to enable that by default. |
@lieryan Thanks for your comments. Yes, I see you are correct--the last match governs. However, because the traversal is partial, exactly what the last match will be can't be described succinctly. |
For the purpose of The alternative is to make |
Actually, because it's a pre-order traversal, the last match is well defined. If you are searching for:
with pattern:
Then the match would be the outer-most last match: Note that returning the last match isn't inherent to |
I am going to close this PR. |
@lieryan Thanks for your comment. Imo it would be good to mention the concept of "outermost-last-match" in the docstring for |
It's not correct to document the "outermost-last-match" in As previously mentioned, the "outermost-last-match" is only true for If you want to document that it's "outermost-last-match" behavior, it would be in |
A preliminary PR for PR #632.
Background
rope.base.ast.call_for_nodes
looks fishy to me:I know of no simple description of what it does.
call_for_nodes
causes multiple unit tests to fail.@lieryan Is there a simple explanation for what
call_for_nodes
does?Summary of changes
call_for_nodes
where therecursive
kwarg is (implicitly) false.See
patched_ast
and_PatchingASTWalker._handle
.recursive
kwarg fromcall_for_nodes
.call_for_nodes
in_ASTMatcher.find_matches
.call_for_nodes
in_ResultChecker._find_node
.call_for_nodes
itself.Why call_for_nodes should not be a utility
I understand neither what
_ASTMatcher.find_matches
and_ResultChecker._find_node
are supposed to do nor how they do it. Imo, these strange traversals should be presented adjacent to the search code. Duplicating these traversals is better than creating a strange helper.