Skip to content

Commit 1ad204d

Browse files
committed
Resolve names using annotation scopes
1 parent 4d2c744 commit 1ad204d

File tree

7 files changed

+264
-75
lines changed

7 files changed

+264
-75
lines changed

src/_griffe/agents/inspector.py

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -338,10 +338,10 @@ def inspect_class(self, node: ObjectNode) -> None:
338338
name=node.name,
339339
docstring=self._get_docstring(node),
340340
bases=bases,
341-
type_parameters=TypeParameters(*_convert_type_parameters(node.obj, self.current)),
342341
lineno=lineno,
343342
endlineno=endlineno,
344343
)
344+
class_.type_parameters = TypeParameters(*_convert_type_parameters(node.obj, self.current, class_))
345345
self.current.set_member(node.name, class_)
346346
self.current = class_
347347
self.extensions.call("on_instance", node=node, obj=class_, agent=self)
@@ -446,18 +446,10 @@ def handle_function(self, node: ObjectNode, labels: set | None = None) -> None:
446446
except Exception: # noqa: BLE001
447447
# so many exceptions can be raised here:
448448
# AttributeError, NameError, RuntimeError, ValueError, TokenError, TypeError
449-
parameters = None
450-
returns = None
449+
signature = None
450+
return_annotation = None
451451
else:
452-
parameters = Parameters(
453-
*[_convert_parameter(parameter, parent=self.current) for parameter in signature.parameters.values()],
454-
)
455-
return_annotation = signature.return_annotation
456-
returns = (
457-
None
458-
if return_annotation is _empty
459-
else _convert_object_to_annotation(return_annotation, parent=self.current)
460-
)
452+
return_annotation = signature.return_annotation # type: ignore[union-attr]
461453

462454
lineno, endlineno = self._get_linenos(node)
463455

@@ -467,21 +459,41 @@ def handle_function(self, node: ObjectNode, labels: set | None = None) -> None:
467459
obj = Attribute(
468460
name=node.name,
469461
value=None,
470-
annotation=returns,
471462
docstring=self._get_docstring(node),
472463
lineno=lineno,
473464
endlineno=endlineno,
474465
)
466+
if return_annotation is not None:
467+
obj.annotation = (
468+
None
469+
if return_annotation is _empty
470+
else _convert_object_to_annotation(
471+
return_annotation,
472+
parent=self.current,
473+
annotation_scope=self.current if self.current.is_class else None, # type: ignore[arg-type]
474+
)
475+
)
475476
else:
476477
obj = Function(
477478
name=node.name,
478-
parameters=parameters,
479-
returns=returns,
480-
type_parameters=TypeParameters(*_convert_type_parameters(node.obj, self.current)),
481479
docstring=self._get_docstring(node),
482480
lineno=lineno,
483481
endlineno=endlineno,
484482
)
483+
obj.type_parameters = TypeParameters(*_convert_type_parameters(node.obj, self.current, obj))
484+
if signature is not None:
485+
obj.parameters = Parameters(
486+
*[
487+
_convert_parameter(parameter, parent=self.current, annotation_scope=obj)
488+
for parameter in signature.parameters.values()
489+
],
490+
)
491+
obj.returns = (
492+
None
493+
if return_annotation is _empty
494+
else _convert_object_to_annotation(return_annotation, parent=self.current, annotation_scope=obj)
495+
)
496+
485497
obj.labels |= labels
486498
self.current.set_member(node.name, obj)
487499
self.extensions.call("on_instance", node=node, obj=obj, agent=self)
@@ -503,13 +515,13 @@ def inspect_type_alias(self, node: ObjectNode) -> None:
503515

504516
type_alias = TypeAlias(
505517
name=node.name,
506-
value=_convert_type_to_annotation(node.obj.__value__, self.current),
507518
lineno=lineno,
508519
endlineno=endlineno,
509-
type_parameters=TypeParameters(*_convert_type_parameters(node.obj, self.current)),
510520
docstring=self._get_docstring(node),
511521
parent=self.current,
512522
)
523+
type_alias.value = _convert_type_to_annotation(node.obj.__value__, self.current, type_alias)
524+
type_alias.type_parameters = TypeParameters(*_convert_type_parameters(node.obj, self.current, type_alias))
513525
self.current.set_member(node.name, type_alias)
514526
self.extensions.call("on_instance", node=node, obj=type_alias, agent=self)
515527
self.extensions.call("on_type_alias_instance", node=node, type_alias=type_alias, agent=self)
@@ -579,10 +591,16 @@ def handle_attribute(self, node: ObjectNode, annotation: str | Expr | None = Non
579591
}
580592

581593

582-
def _convert_parameter(parameter: SignatureParameter, parent: Module | Class) -> Parameter:
594+
def _convert_parameter(
595+
parameter: SignatureParameter,
596+
parent: Module | Class,
597+
annotation_scope: Function | Class | TypeAlias,
598+
) -> Parameter:
583599
name = parameter.name
584600
annotation = (
585-
None if parameter.annotation is _empty else _convert_object_to_annotation(parameter.annotation, parent=parent)
601+
None
602+
if parameter.annotation is _empty
603+
else _convert_object_to_annotation(parameter.annotation, parent=parent, annotation_scope=annotation_scope)
586604
)
587605
kind = _parameter_kind_map[parameter.kind]
588606
if parameter.default is _empty:
@@ -595,7 +613,11 @@ def _convert_parameter(parameter: SignatureParameter, parent: Module | Class) ->
595613
return Parameter(name, annotation=annotation, kind=kind, default=default)
596614

597615

598-
def _convert_object_to_annotation(obj: Any, parent: Module | Class) -> str | Expr | None:
616+
def _convert_object_to_annotation(
617+
obj: Any,
618+
parent: Module | Class,
619+
annotation_scope: Function | Class | TypeAlias | None,
620+
) -> str | Expr | None:
599621
# even when *we* import future annotations,
600622
# the object from which we get a signature
601623
# can come from modules which did *not* import them,
@@ -612,7 +634,7 @@ def _convert_object_to_annotation(obj: Any, parent: Module | Class) -> str | Exp
612634
annotation_node = compile(obj, mode="eval", filename="<>", flags=ast.PyCF_ONLY_AST, optimize=2)
613635
except SyntaxError:
614636
return obj
615-
return safe_get_annotation(annotation_node.body, parent=parent) # type: ignore[attr-defined]
637+
return safe_get_annotation(annotation_node.body, parent=parent, annotation_scope=annotation_scope) # type: ignore[attr-defined]
616638

617639

618640
_type_parameter_kind_map = {
@@ -630,6 +652,7 @@ def _convert_object_to_annotation(obj: Any, parent: Module | Class) -> str | Exp
630652
def _convert_type_parameters(
631653
obj: Any,
632654
parent: Module | Class,
655+
annotation_scope: Function | Class | TypeAlias,
633656
) -> list[TypeParameter]:
634657
obj = unwrap(obj)
635658

@@ -640,16 +663,17 @@ def _convert_type_parameters(
640663
for type_parameter in obj.__type_params__:
641664
bound = getattr(type_parameter, "__bound__", None)
642665
if bound is not None:
643-
bound = _convert_type_to_annotation(bound, parent=parent)
666+
bound = _convert_type_to_annotation(bound, parent=parent, annotation_scope=annotation_scope)
644667
constraints: list[str | Expr] = [
645-
_convert_type_to_annotation(constraint, parent=parent) # type: ignore[misc]
668+
_convert_type_to_annotation(constraint, parent=parent, annotation_scope=annotation_scope) # type: ignore[misc]
646669
for constraint in getattr(type_parameter, "__constraints__", ())
647670
]
648671

649672
if getattr(type_parameter, "has_default", lambda: False)():
650673
default = _convert_type_to_annotation(
651674
type_parameter.__default__,
652675
parent=parent,
676+
annotation_scope=annotation_scope,
653677
)
654678
else:
655679
default = None
@@ -667,22 +691,27 @@ def _convert_type_parameters(
667691
return type_parameters
668692

669693

670-
def _convert_type_to_annotation(obj: Any, parent: Module | Class) -> str | Expr | None:
694+
def _convert_type_to_annotation(
695+
obj: Any,
696+
parent: Module | Class,
697+
annotation_scope: Function | Class | TypeAlias,
698+
) -> str | Expr | None:
671699
origin = typing.get_origin(obj)
672700

673701
if origin is None:
674-
return _convert_object_to_annotation(obj, parent=parent)
702+
return _convert_object_to_annotation(obj, parent=parent, annotation_scope=annotation_scope)
675703

676704
args: Sequence[str | Expr | None] = [
677-
_convert_type_to_annotation(arg, parent=parent) for arg in typing.get_args(obj)
705+
_convert_type_to_annotation(arg, parent=parent, annotation_scope=annotation_scope)
706+
for arg in typing.get_args(obj)
678707
]
679708

680709
# YORE: EOL 3.9: Replace block with lines 2-3.
681710
if sys.version_info >= (3, 10):
682711
if origin is types.UnionType:
683712
return functools.reduce(lambda left, right: ExprBinOp(left, "|", right), args) # type: ignore[arg-type]
684713

685-
origin = _convert_type_to_annotation(origin, parent=parent)
714+
origin = _convert_type_to_annotation(origin, parent=parent, annotation_scope=annotation_scope)
686715
if origin is None:
687716
return None
688717

src/_griffe/agents/visitor.py

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,22 @@ def _get_docstring(self, node: ast.AST, *, strict: bool = False) -> Docstring |
215215
def _get_type_parameters(
216216
self,
217217
statement: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.TypeAlias,
218+
annotation_scope: Function | Class | TypeAlias,
218219
) -> list[TypeParameter]:
219220
return [
220221
TypeParameter(
221222
type_param.name, # type: ignore[attr-defined]
222223
kind=self._type_parameter_kind_map[type(type_param)],
223-
bound=safe_get_annotation(getattr(type_param, "bound", None), parent=self.current),
224-
default=safe_get_annotation(getattr(type_param, "default_value", None), parent=self.current),
224+
bound=safe_get_annotation(
225+
getattr(type_param, "bound", None),
226+
parent=self.current,
227+
annotation_scope=annotation_scope,
228+
),
229+
default=safe_get_annotation(
230+
getattr(type_param, "default_value", None),
231+
parent=self.current,
232+
annotation_scope=annotation_scope,
233+
),
225234
)
226235
for type_param in statement.type_params
227236
]
@@ -230,6 +239,7 @@ def _get_type_parameters(
230239
def _get_type_parameters(
231240
self,
232241
_statement: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef,
242+
_obj: Function | Class | TypeAlias,
233243
) -> list[TypeParameter]:
234244
return []
235245

@@ -316,20 +326,20 @@ def visit_classdef(self, node: ast.ClassDef) -> None:
316326
else:
317327
lineno = node.lineno
318328

319-
# handle base classes
320-
bases = [safe_get_base_class(base, parent=self.current) for base in node.bases]
321-
322329
class_ = Class(
323330
name=node.name,
324331
lineno=lineno,
325332
endlineno=node.end_lineno,
326333
docstring=self._get_docstring(node),
327334
decorators=decorators,
328-
type_parameters=TypeParameters(*self._get_type_parameters(node)),
329-
bases=bases, # type: ignore[arg-type]
330335
runtime=not self.type_guarded,
331336
)
337+
338+
# handle base classes
339+
class_.bases = [safe_get_base_class(base, parent=self.current, annotation_scope=class_) for base in node.bases] # type: ignore[misc]
340+
class_.type_parameters = TypeParameters(*self._get_type_parameters(node, class_))
332341
class_.labels |= self.decorators_to_labels(decorators)
342+
333343
self.current.set_member(node.name, class_)
334344
self.current = class_
335345
self.extensions.call("on_instance", node=node, obj=class_, agent=self)
@@ -418,45 +428,48 @@ def handle_function(self, node: ast.AsyncFunctionDef | ast.FunctionDef, labels:
418428
attribute = Attribute(
419429
name=node.name,
420430
value=None,
421-
annotation=safe_get_annotation(node.returns, parent=self.current),
422431
lineno=node.lineno,
423432
endlineno=node.end_lineno,
424433
docstring=self._get_docstring(node),
425434
runtime=not self.type_guarded,
426435
)
436+
attribute.annotation = safe_get_annotation(
437+
node.returns,
438+
parent=self.current,
439+
annotation_scope=self.current if not self.current.is_module else None, # type: ignore[arg-type]
440+
)
427441
attribute.labels |= labels
428442
self.current.set_member(node.name, attribute)
429443
self.extensions.call("on_instance", node=node, obj=attribute, agent=self)
430444
self.extensions.call("on_attribute_instance", node=node, attr=attribute, agent=self)
431445
return
432446

447+
function = Function(
448+
name=node.name,
449+
lineno=lineno,
450+
endlineno=node.end_lineno,
451+
decorators=decorators,
452+
docstring=self._get_docstring(node),
453+
runtime=not self.type_guarded,
454+
parent=self.current,
455+
)
456+
433457
# handle parameters
434-
parameters = Parameters(
458+
function.parameters = Parameters(
435459
*[
436460
Parameter(
437461
name,
438462
kind=kind,
439-
annotation=safe_get_annotation(annotation, parent=self.current),
463+
annotation=safe_get_annotation(annotation, parent=self.current, annotation_scope=function),
440464
default=default
441465
if isinstance(default, str)
442466
else safe_get_expression(default, parent=self.current, parse_strings=False),
443467
)
444468
for name, annotation, kind, default in get_parameters(node.args)
445469
],
446470
)
447-
448-
function = Function(
449-
name=node.name,
450-
lineno=lineno,
451-
endlineno=node.end_lineno,
452-
parameters=parameters,
453-
returns=safe_get_annotation(node.returns, parent=self.current),
454-
decorators=decorators,
455-
type_parameters=TypeParameters(*self._get_type_parameters(node)),
456-
docstring=self._get_docstring(node),
457-
runtime=not self.type_guarded,
458-
parent=self.current,
459-
)
471+
function.returns = safe_get_annotation(node.returns, parent=self.current, annotation_scope=function)
472+
function.type_parameters = TypeParameters(*self._get_type_parameters(node, function))
460473

461474
property_function = self.get_base_property(decorators, function)
462475

@@ -519,22 +532,22 @@ def visit_typealias(self, node: ast.TypeAlias) -> None:
519532

520533
name = node.name.id
521534

522-
value = safe_get_expression(node.value, parent=self.current)
523-
524535
try:
525536
docstring = self._get_docstring(ast_next(node), strict=True)
526537
except (LastNodeError, AttributeError):
527538
docstring = None
528539

529540
type_alias = TypeAlias(
530541
name=name,
531-
value=value,
532542
lineno=node.lineno,
533543
endlineno=node.end_lineno,
534-
type_parameters=TypeParameters(*self._get_type_parameters(node)),
535544
docstring=docstring,
536545
parent=self.current,
537546
)
547+
548+
type_alias.value = safe_get_annotation(node.value, parent=self.current, annotation_scope=type_alias)
549+
type_alias.type_parameters = TypeParameters(*self._get_type_parameters(node, type_alias))
550+
538551
self.current.set_member(name, type_alias)
539552
self.extensions.call("on_instance", node=node, obj=type_alias, agent=self)
540553
self.extensions.call("on_type_alias_instance", node=node, type_alias=type_alias, agent=self)
@@ -711,7 +724,14 @@ def visit_annassign(self, node: ast.AnnAssign) -> None:
711724
Parameters:
712725
node: The node to visit.
713726
"""
714-
self.handle_attribute(node, safe_get_annotation(node.annotation, parent=self.current))
727+
self.handle_attribute(
728+
node,
729+
safe_get_annotation(
730+
node.annotation,
731+
parent=self.current,
732+
annotation_scope=self.current if not self.current.is_module else None, # type: ignore[arg-type]
733+
),
734+
)
715735

716736
def visit_augassign(self, node: ast.AugAssign) -> None:
717737
"""Visit an augmented assignment node.

src/_griffe/docstrings/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,15 @@ def parse_docstring_annotation(
7070
):
7171
code = compile(annotation, mode="eval", filename="", flags=PyCF_ONLY_AST, optimize=2)
7272
if code.body: # type: ignore[attr-defined]
73+
annotation_scope = docstring.parent
74+
if annotation_scope is not None and annotation_scope.is_attribute:
75+
annotation_scope = annotation_scope.parent
76+
if annotation_scope is not None and annotation_scope.is_module:
77+
annotation_scope = None
7378
name_or_expr = safe_get_annotation(
7479
code.body, # type: ignore[attr-defined]
7580
parent=docstring.parent, # type: ignore[arg-type]
81+
annotation_scope=annotation_scope, # type: ignore[arg-type]
7682
log_level=log_level,
7783
)
7884
return name_or_expr or annotation

0 commit comments

Comments
 (0)