Skip to content

Commit 3f98bc8

Browse files
committed
infer-for-properties
1 parent 79f313a commit 3f98bc8

1 file changed

Lines changed: 60 additions & 3 deletions

File tree

IPython/core/guarded_eval.py

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -658,9 +658,14 @@ def eval_node(node: Union[ast.AST, None], context: EvaluationContext):
658658
return_type = eval_node(node.returns, context=context)
659659

660660
if is_property:
661-
context.transient_locals[node.name] = _resolve_annotation(
662-
return_type, context
663-
)
661+
if return_type is not None:
662+
context.transient_locals[node.name] = _resolve_annotation(
663+
return_type, context
664+
)
665+
else:
666+
inferred_type = _infer_property_return_type(node, context)
667+
context.transient_locals[node.name] = inferred_type
668+
664669
return None
665670

666671
def dummy_function(*args, **kwargs):
@@ -853,6 +858,58 @@ def dummy_function(*args, **kwargs):
853858
return None
854859

855860

861+
def _infer_property_return_type(node: ast.FunctionDef, context: EvaluationContext):
862+
"""Infer the return type of a property by executing its body."""
863+
temp_context = EvaluationContext(
864+
globals=context.globals,
865+
locals=context.locals,
866+
evaluation=context.evaluation,
867+
in_subscript=context.in_subscript,
868+
transient_locals={},
869+
)
870+
871+
for stmt in node.body:
872+
if isinstance(stmt, ast.Return) and stmt.value is not None:
873+
try:
874+
return_value = eval_node(stmt.value, temp_context)
875+
if return_value is not None and return_value is not NOT_EVALUATED:
876+
temp = _create_duck_from_value(return_value)
877+
return temp
878+
except Exception:
879+
pass
880+
return None
881+
882+
883+
def _create_duck_from_value(value):
884+
"""Create a Duck object from an actual runtime value."""
885+
if value is None or value is NOT_EVALUATED:
886+
return None
887+
value_type = type(value)
888+
if isinstance(value, dict):
889+
return _Duck(
890+
attributes=dict.fromkeys(dir(dict())), items=value if value else {}
891+
)
892+
elif isinstance(value, list):
893+
element_duck = None
894+
if value:
895+
element_duck = _create_duck_from_value(value[0])
896+
return _Duck(
897+
attributes=dict.fromkeys(dir(list())),
898+
items=_GetItemDuck(lambda: element_duck),
899+
)
900+
elif isinstance(value, set):
901+
return _Duck(attributes=dict.fromkeys(dir(set())))
902+
elif isinstance(value, tuple):
903+
return value
904+
elif isinstance(value, (str, int, float, bool, bytes)):
905+
return _Duck(attributes=dict.fromkeys(dir(value_type())))
906+
else:
907+
try:
908+
return _create_duck_for_heap_type(value_type)
909+
except Exception:
910+
return _Duck(attributes=dict.fromkeys(dir(value)))
911+
912+
856913
def _eval_return_type(func: Callable, node: ast.Call, context: EvaluationContext):
857914
"""Evaluate return type of a given callable function.
858915

0 commit comments

Comments
 (0)