@@ -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+
856913def _eval_return_type (func : Callable , node : ast .Call , context : EvaluationContext ):
857914 """Evaluate return type of a given callable function.
858915
0 commit comments