@@ -750,13 +750,20 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
750750 argdefs = annotate .__defaults__ ,
751751 kwdefaults = annotate .__kwdefaults__ ,
752752 )
753- annos = func (Format .VALUE_WITH_FAKE_GLOBALS )
753+ try :
754+ annos = func (Format .VALUE_WITH_FAKE_GLOBALS )
755+ except ValueError :
756+ # Dict comprehensions such as `{k: v for k, v in items}` unpack
757+ # each iterated element. Fake-globals iteration yields a single
758+ # starred stringifier, so unpacking raises ValueError. Recover
759+ # the original annotation text from source when we can.
760+ sourced = _string_annotations_from_source (owner )
761+ if sourced is not None :
762+ return sourced
763+ raise
754764 if _is_evaluate :
755765 return _stringify_single (annos )
756- return {
757- key : _stringify_single (val )
758- for key , val in annos .items ()
759- }
766+ return _stringify_annotation_dict (annos , owner )
760767 elif format == Format .FORWARDREF :
761768 # FORWARDREF is implemented similarly to STRING, but there are two changes,
762769 # at the beginning and the end of the process.
@@ -878,6 +885,81 @@ def _build_closure(annotate, owner, is_class, stringifier_dict, *, allow_evaluat
878885 return tuple (new_closure ), cell_dict
879886
880887
888+ def _string_annotations_from_source (obj ):
889+ """Best-effort STRING annotations reconstructed from *obj*'s source AST.
890+
891+ Used when fake-globals evaluation cannot stringify an annotation (dict
892+ comprehensions, lambdas, generator expressions). Returns None if source
893+ is unavailable. inspect is imported lazily because it imports this module.
894+ """
895+ if obj is None :
896+ return None
897+ try :
898+ import inspect
899+ import textwrap
900+ source = inspect .getsource (obj )
901+ except (OSError , TypeError , RecursionError ):
902+ return None
903+ source = textwrap .dedent (source )
904+ try :
905+ tree = ast .parse (source )
906+ except SyntaxError :
907+ return None
908+ if not tree .body :
909+ return None
910+ node = tree .body [0 ]
911+ result = {}
912+ if isinstance (node , (ast .FunctionDef , ast .AsyncFunctionDef )):
913+ for arg in (
914+ * node .args .posonlyargs ,
915+ * node .args .args ,
916+ * node .args .kwonlyargs ,
917+ ):
918+ if arg .annotation is not None :
919+ result [arg .arg ] = ast .unparse (arg .annotation )
920+ if node .args .vararg is not None and node .args .vararg .annotation is not None :
921+ result [node .args .vararg .arg ] = ast .unparse (node .args .vararg .annotation )
922+ if node .args .kwarg is not None and node .args .kwarg .annotation is not None :
923+ result [node .args .kwarg .arg ] = ast .unparse (node .args .kwarg .annotation )
924+ if node .returns is not None :
925+ result ["return" ] = ast .unparse (node .returns )
926+ return result
927+ if isinstance (node , ast .ClassDef ):
928+ for stmt in node .body :
929+ if isinstance (stmt , ast .AnnAssign ) and isinstance (stmt .target , ast .Name ):
930+ result [stmt .target .id ] = ast .unparse (stmt .annotation )
931+ return result
932+ return None
933+
934+
935+ def _is_runtime_constructed (value ):
936+ """True if *value* was created at annotation-eval time and has no AST.
937+
938+ Lambdas and generator expressions are syntax, not name lookups, so the
939+ fake-globals stringifier never sees them. Their repr() embeds a memory
940+ address and is not a valid annotation string.
941+ """
942+ return isinstance (value , (
943+ types .FunctionType ,
944+ types .BuiltinFunctionType ,
945+ types .MethodType ,
946+ types .GeneratorType ,
947+ types .AsyncGeneratorType ,
948+ types .CoroutineType ,
949+ ))
950+
951+
952+ def _stringify_annotation_dict (annos , owner ):
953+ sourced = _string_annotations_from_source (owner )
954+ result = {}
955+ for key , val in annos .items ():
956+ if sourced is not None and key in sourced and _is_runtime_constructed (val ):
957+ result [key ] = sourced [key ]
958+ else :
959+ result [key ] = _stringify_single (val )
960+ return result
961+
962+
881963def _stringify_single (anno ):
882964 if anno is ...:
883965 return "..."
@@ -886,6 +968,10 @@ def _stringify_single(anno):
886968 return anno
887969 elif isinstance (anno , _Template ):
888970 return ast .unparse (_template_to_ast (anno ))
971+ elif _is_runtime_constructed (anno ):
972+ # Lambdas and generator expressions are syntax, not name lookups.
973+ # repr() embeds a memory address; type_repr() is stable.
974+ return type_repr (anno )
889975 else :
890976 return repr (anno )
891977
@@ -1093,6 +1179,13 @@ def type_repr(value):
10931179 if value .__module__ == "builtins" :
10941180 return value .__qualname__
10951181 return f"{ value .__module__ } .{ value .__qualname__ } "
1182+ elif isinstance (value , (
1183+ types .GeneratorType ,
1184+ types .AsyncGeneratorType ,
1185+ types .CoroutineType ,
1186+ )):
1187+ # repr() of these objects embeds a memory address.
1188+ return value .__qualname__
10961189 elif isinstance (value , _Template ):
10971190 tree = _template_to_ast (value )
10981191 return ast .unparse (tree )
0 commit comments