-
Notifications
You must be signed in to change notification settings - Fork 49
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
Visit Try 'orelse', 'finalbody' and 'handlers' and If 'orelse' #589
Changes from 6 commits
6f2e374
0c50517
7d40873
ae6582f
3bd4407
8613422
9a02722
7ad998f
bd90f2b
39ca9a3
0ad23b8
a5314c9
d4fb2be
d38659e
9cfb06a
db51139
5e9a6fc
8c61bc1
312b301
872ec07
505e113
a100a16
0ea5b7c
5a259d2
52d0713
99b8205
77099c5
2f5cfc9
f9022f3
3d3b730
d393249
2dd25e1
4c00191
1993e51
8f82e87
0650c35
8bcfb56
b3957fc
3189f7c
9f576de
ecfa367
6acffaf
e96b927
6a542ed
2235f98
fdc2755
0278320
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,6 +1,7 @@ | ||||||
"""Convert ASTs into L{pydoctor.model.Documentable} instances.""" | ||||||
|
||||||
import ast | ||||||
import contextlib | ||||||
import sys | ||||||
from attr import attrs, attrib | ||||||
from functools import partial | ||||||
|
@@ -201,7 +202,39 @@ def __init__(self, builder: 'ASTBuilder', module: model.Module): | |||||
self.builder = builder | ||||||
self.system = builder.system | ||||||
self.module = module | ||||||
self._override_guard_state: Tuple[bool, model.Documentable, Sequence[str]] = \ | ||||||
(False, cast(model.Documentable, None), []) | ||||||
|
||||||
@contextlib.contextmanager | ||||||
def override_guard(self) -> Iterator[None]: | ||||||
""" | ||||||
Returns a context manager that will make the builder ignore any extraneous | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
assigments to existing names within the same context. | ||||||
tristanlatr marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
@note: The list of existing names is generated at the moment of | ||||||
calling the function. | ||||||
tristanlatr marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
""" | ||||||
current = self.builder.current | ||||||
ignore_override_init = self._override_guard_state | ||||||
# we list names only once to ignore new names added inside the block, | ||||||
# they should be overriden as usual. | ||||||
self._override_guard_state = (True, current, self._list_names(current)) | ||||||
yield | ||||||
self._override_guard_state = ignore_override_init | ||||||
|
||||||
def _list_names(self, ob: model.Documentable) -> List[str]: | ||||||
""" | ||||||
List the names currently defined in a class/module. | ||||||
""" | ||||||
names = list(ob.contents) | ||||||
if isinstance(ob, model.CanContainImportsDocumentable): | ||||||
names.extend(ob._localNameToFullName_map) | ||||||
return names | ||||||
|
||||||
def _name_in_override_guard(self, ob: model.Documentable, name:str) -> bool: | ||||||
tristanlatr marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
return self._override_guard_state[0] is True \ | ||||||
and self._override_guard_state[1] is ob \ | ||||||
and name in self._override_guard_state[2] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @glyph please see this method for the implementation of the override guard. The override guard is a re-entrant context manager that ensures that follow-up assignments to names that already existed before in the same frame context are simply ignored. |
||||||
|
||||||
def visit_If(self, node: ast.If) -> None: | ||||||
if isinstance(node.test, ast.Compare): | ||||||
|
@@ -210,6 +243,28 @@ def visit_If(self, node: ast.If) -> None: | |||||
# whatever is declared in them cannot be imported | ||||||
# and thus is not part of the API | ||||||
raise self.SkipNode() | ||||||
|
||||||
def depart_If(self, node: ast.If) -> None: | ||||||
# At this point the body of the Try node has already been visited | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
# Visit the 'orelse' block of the If node, with override guard | ||||||
with self.override_guard(): | ||||||
for n in node.orelse: | ||||||
self.walkabout(n) | ||||||
|
||||||
def depart_Try(self, node: ast.Try) -> None: | ||||||
# At this point the body of the Try node has already been visited | ||||||
# Visit the 'orelse' and 'finalbody' blocks of the Try node. | ||||||
|
||||||
for n in node.orelse: | ||||||
self.walkabout(n) | ||||||
for n in node.finalbody: | ||||||
self.walkabout(n) | ||||||
|
||||||
# Visit the handlers with override guard | ||||||
with self.override_guard(): | ||||||
for h in node.handlers: | ||||||
for n in h.body: | ||||||
self.walkabout(n) | ||||||
|
||||||
def visit_Module(self, node: ast.Module) -> None: | ||||||
assert self.module.docstring is None | ||||||
|
@@ -227,6 +282,9 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None: | |||||
parent = self.builder.current | ||||||
if isinstance(parent, model.Function): | ||||||
raise self.SkipNode() | ||||||
# Ignore in override guard | ||||||
if self._name_in_override_guard(parent, node.name): | ||||||
raise self.SkipNode() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder why this code is required, a module under an if branch is not possible. I also wonder how this code is not marked as unreached by the tests… |
||||||
|
||||||
rawbases = [] | ||||||
bases = [] | ||||||
|
@@ -328,6 +386,11 @@ def visit_ImportFrom(self, node: ast.ImportFrom) -> None: | |||||
def _importAll(self, modname: str) -> None: | ||||||
"""Handle a C{from <modname> import *} statement.""" | ||||||
|
||||||
# Always ignore import * in override guard | ||||||
tristanlatr marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
if self._override_guard_state[0]: | ||||||
self.builder.warning("ignored import * from", modname) | ||||||
tristanlatr marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
return | ||||||
|
||||||
mod = self.system.getProcessedModule(modname) | ||||||
if mod is None: | ||||||
# We don't have any information about the module, so we don't know | ||||||
|
@@ -420,7 +483,11 @@ def _importNames(self, modname: str, names: Iterable[ast.alias]) -> None: | |||||
orgname, asname = al.name, al.asname | ||||||
if asname is None: | ||||||
asname = orgname | ||||||
|
||||||
|
||||||
# Ignore in override guard | ||||||
if self._name_in_override_guard(current, asname): | ||||||
continue | ||||||
|
||||||
if mod is not None and self._handleReExport(exports, orgname, asname, mod) is True: | ||||||
continue | ||||||
|
||||||
|
@@ -449,9 +516,14 @@ def visit_Import(self, node: ast.Import) -> None: | |||||
str(self.builder.current)) | ||||||
return | ||||||
_localNameToFullName = self.builder.current._localNameToFullName_map | ||||||
current = self.builder.current | ||||||
for al in node.names: | ||||||
fullname, asname = al.name, al.asname | ||||||
# Ignore in override guard | ||||||
if self._name_in_override_guard(current, asname or fullname): | ||||||
continue | ||||||
if asname is not None: | ||||||
# Do not create an alias with the same name as value | ||||||
_localNameToFullName[asname] = fullname | ||||||
|
||||||
|
||||||
|
@@ -729,6 +801,8 @@ def _handleAssignment(self, | |||||
if isinstance(targetNode, ast.Name): | ||||||
target = targetNode.id | ||||||
scope = self.builder.current | ||||||
if self._name_in_override_guard(scope, target): | ||||||
return | ||||||
if isinstance(scope, model.Module): | ||||||
self._handleAssignmentInModule(target, annotation, expr, lineno) | ||||||
elif isinstance(scope, model.Class): | ||||||
|
@@ -788,6 +862,9 @@ def _handleFunctionDef(self, | |||||
parent = self.builder.current | ||||||
if isinstance(parent, model.Function): | ||||||
raise self.SkipNode() | ||||||
# Ignore in override guard | ||||||
if self._name_in_override_guard(parent, node.name): | ||||||
raise self.SkipNode() | ||||||
|
||||||
lineno = node.lineno | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A clearer explanation of the definition of "extraneous" would be interesting here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extraneous meaning assignments to name that already exist
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can read: "Returns a context manager that will make the builder ignore any new assignments to names that already existed in the same context"