@@ -178,14 +178,31 @@ def _is_annotated_form(obj: Any) -> TypeIs[Annotated[Any, ...]]:
178178 return typing .get_origin (obj ) is Annotated or str (obj ).startswith ('typing.Annotated' )
179179
180180
181+ def _is_unpack_form (obj : Any ) -> bool :
182+ """Check if the object is :class:`typing.Unpack` or equivalent."""
183+ if sys .version_info >= (3 , 11 ):
184+ from typing import Unpack
185+
186+ # typing_extensions.Unpack != typing.Unpack for 3.11, but we assume
187+ # that typing_extensions.Unpack should not be used in that case
188+ return typing .get_origin (obj ) is Unpack
189+
190+ # 3.9 and 3.10 require typing_extensions.Unpack
191+ origin = typing .get_origin (obj )
192+ return (
193+ getattr (origin , '__module__' , None ) == 'typing_extensions'
194+ and _typing_internal_name (origin ) == 'Unpack'
195+ )
196+
197+
181198def _typing_internal_name (obj : Any ) -> str | None :
182199 if sys .version_info [:2 ] >= (3 , 10 ):
183200 return obj .__name__
184201 return getattr (obj , '_name' , None )
185202
186203
187204def restify (cls : Any , mode : _RestifyMode = 'fully-qualified-except-typing' ) -> str :
188- """Convert python class to a reST reference.
205+ """Convert a type-like object to a reST reference.
189206
190207 :param mode: Specify a method how annotations will be stringified.
191208
@@ -252,6 +269,9 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s
252269 # *cls* is defined in ``typing``, and thus ``__args__`` must exist
253270 return ' | ' .join (restify (a , mode ) for a in cls .__args__ )
254271 elif inspect .isgenericalias (cls ):
272+ # A generic alias always has an __origin__, but it is difficult to
273+ # use a type guard on inspect.isgenericalias()
274+ # (ideally, we would use ``TypeIs`` introduced in Python 3.13).
255275 cls_name = _typing_internal_name (cls )
256276
257277 if isinstance (cls .__origin__ , typing ._SpecialForm ):
@@ -298,7 +318,7 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s
298318 elif isinstance (cls , ForwardRef ):
299319 return f':py:class:`{ cls .__forward_arg__ } `'
300320 else :
301- # not a class (ex. TypeVar)
321+ # not a class (ex. TypeVar) but should have a __name__
302322 return f':py:obj:`{ module_prefix } { cls .__module__ } .{ cls .__name__ } `'
303323 except (AttributeError , TypeError ):
304324 return inspect .object_description (cls )
@@ -366,7 +386,8 @@ def stringify_annotation(
366386 annotation_module_is_typing = annotation_module == 'typing'
367387
368388 # Extract the annotation's base type by considering formattable cases
369- if isinstance (annotation , TypeVar ):
389+ if isinstance (annotation , TypeVar ) and not _is_unpack_form (annotation ):
390+ # typing_extensions.Unpack is incorrectly determined as a TypeVar
370391 if annotation_module_is_typing and mode in {'fully-qualified-except-typing' , 'smart' }:
371392 return annotation_name
372393 return module_prefix + f'{ annotation_module } .{ annotation_name } '
@@ -391,6 +412,7 @@ def stringify_annotation(
391412 # PEP 585 generic
392413 if not args : # Empty tuple, list, ...
393414 return repr (annotation )
415+
394416 concatenated_args = ', ' .join (stringify_annotation (arg , mode ) for arg in args )
395417 return f'{ annotation_qualname } [{ concatenated_args } ]'
396418 else :
@@ -404,6 +426,8 @@ def stringify_annotation(
404426 module_prefix = f'~{ module_prefix } '
405427 if annotation_module_is_typing and mode == 'fully-qualified-except-typing' :
406428 module_prefix = ''
429+ elif _is_unpack_form (annotation ) and annotation_module == 'typing_extensions' :
430+ module_prefix = '~' if mode == 'smart' else ''
407431 else :
408432 module_prefix = ''
409433
@@ -412,9 +436,8 @@ def stringify_annotation(
412436 # handle ForwardRefs
413437 qualname = annotation_forward_arg
414438 else :
415- _name = getattr (annotation , '_name' , '' )
416- if _name :
417- qualname = _name
439+ if internal_name := _typing_internal_name (annotation ):
440+ qualname = internal_name
418441 elif annotation_qualname :
419442 qualname = annotation_qualname
420443 else :
0 commit comments