@@ -485,3 +485,117 @@ annotations from the class and puts them in a separate attribute:
485485 typ.classvars = classvars # Store the ClassVars in a separate attribute
486486 return typ
487487
488+
489+ Limitations of the ``STRING `` format
490+ ------------------------------------
491+
492+ The :attr: `~Format.STRING ` format is meant to approximate the source code
493+ of the annotation, but the implementation strategy used means that it is not
494+ always possible to recover the exact source code.
495+
496+ First, the stringifier of course cannot recover any information that is not present in
497+ the compiled code, including comments, whitespace, parenthesization, and operations that
498+ get simplified by the compiler.
499+
500+ Second, the stringifier can intercept almost all operations that involve names looked
501+ up in some scope, but it cannot intercept operations that operate fully on constants.
502+ As a corollary, this also means it is not safe to request the ``STRING `` format on
503+ untrusted code: Python is powerful enough that it is possible to achieve arbitrary
504+ code execution even with no access to any globals or builtins. For example:
505+
506+ .. code-block :: pycon
507+
508+ >>> def f(x: (1).__class__.__base__.__subclasses__()[-1].__init__.__builtins__["print"]("Hello world")): pass
509+ ...
510+ >>> annotationlib.get_annotations(f, format=annotationlib.Format.SOURCE)
511+ Hello world
512+ {'x': 'None'}
513+
514+ .. note ::
515+ This particular example works as of the time of writing, but it relies on
516+ implementation details and is not guaranteed to work in the future.
517+
518+ Among the different kinds of expressions that exist in Python,
519+ as represented by the :mod: `ast ` module, some expressions are supported,
520+ meaning that the ``STRING `` format can generally recover the original source code;
521+ others are unsupported, meaning that they may result in incorrect output or an error.
522+
523+ The following are supported (sometimes with caveats):
524+
525+ * :class: `ast.BinOp `
526+ * :class: `ast.UnaryOp `
527+
528+ * :class: `ast.Invert ` (``~ ``), :class: `ast.UAdd ` (``+ ``), and :class: `ast.USub ` (``- ``) are supported
529+ * :class: `ast.Not ` (``not ``) is not supported
530+
531+ * :class: `ast.Dict ` (except when using ``** `` unpacking)
532+ * :class: `ast.Set `
533+ * :class: `ast.Compare `
534+
535+ * :class: `ast.Eq ` and :class: `ast.NotEq ` are supported
536+ * :class: `ast.Lt `, :class: `ast.LtE `, :class: `ast.Gt `, and :class: `ast.GtE ` are supported, but the operand may be flipped
537+ * :class: `ast.Is `, :class: `ast.IsNot `, :class: `ast.In `, and :class: `ast.NotIn ` are not supported
538+
539+ * :class: `ast.Call ` (except when using ``** `` unpacking)
540+ * :class: `ast.Constant ` (though not the exact representation of the constant; for example, escape
541+ sequences in strings are lost; hexadecimal numbers are converted to decimal)
542+ * :class: `ast.Attribute ` (assuming the value is not a constant)
543+ * :class: `ast.Subscript ` (assuming the value is not a constant)
544+ * :class: `ast.Starred ` (``* `` unpacking)
545+ * :class: `ast.Name `
546+ * :class: `ast.List `
547+ * :class: `ast.Tuple `
548+ * :class: `ast.Slice `
549+
550+ The following are unsupported, but throw an informative error when encountered by the
551+ stringifier:
552+
553+ * :class: `ast.FormattedValue ` (f-strings; error is not detected if conversion specifiers like ``!r ``
554+ are used)
555+ * :class: `ast.JoinedStr ` (f-strings)
556+
557+ The following are unsupported and result in incorrect output:
558+
559+ * :class: `ast.BoolOp ` (``and `` and ``or ``)
560+ * :class: `ast.IfExp `
561+ * :class: `ast.Lambda `
562+ * :class: `ast.ListComp `
563+ * :class: `ast.SetComp `
564+ * :class: `ast.DictComp `
565+ * :class: `ast.GeneratorExp `
566+
567+ The following are disallowed in annotation scopes and therefore not relevant:
568+
569+ * :class: `ast.NamedExpr ` (``:= ``)
570+ * :class: `ast.Await `
571+ * :class: `ast.Yield `
572+ * :class: `ast.YieldFrom `
573+
574+
575+ Limitations of the ``FORWARDREF `` format
576+ ----------------------------------------
577+
578+ The :attr: `~Format.FORWARDREF ` format aims to produce real values as much
579+ as possible, with anything that cannot be resolved replaced with
580+ :class: `ForwardRef ` objects. It is affected by broadly the same Limitations
581+ as the :attr: `~Format.STRING ` format: annotations that perform operations on
582+ literals or that use unsupported expression types may raise exceptions when
583+ evaluated using the :attr: `~Format.FORWARDREF ` format.
584+
585+ Below are a few examples of the behavior with unsupported expressions:
586+
587+ .. code-block :: pycon
588+
589+ >>> from annotationlib import get_annotations, Format
590+ >>> def zerodiv(x: 1 / 0): ...
591+ >>> get_annotations(zerodiv, format=Format.STRING)
592+ Traceback (most recent call last):
593+ ...
594+ ZeroDivisionError: division by zero
595+ >>> get_annotations(zerodiv, format=Format.FORWARDREF)
596+ Traceback (most recent call last):
597+ ...
598+ ZeroDivisionError: division by zero
599+ >>> def ifexp(x: 1 if y else 0): ...
600+ >>> get_annotations(ifexp, format=Format.STRING)
601+ {'x': '1'}
0 commit comments