@@ -127,34 +127,34 @@ Classes
127127
128128 Values are the result of evaluating the annotation expressions.
129129
130- .. attribute :: FORWARDREF
130+ .. attribute :: VALUE_WITH_FAKE_GLOBALS
131131 :value: 2
132132
133+ Special value used to signal that an annotate function is being
134+ evaluated in a special environment with fake globals. When passed this
135+ value, annotate functions should either return the same value as for
136+ the :attr: `Format.VALUE ` format, or raise :exc: `NotImplementedError `
137+ to signal that they do not support execution in this environment.
138+ This format is only used internally and should not be passed to
139+ the functions in this module.
140+
141+ .. attribute :: FORWARDREF
142+ :value: 3
143+
133144 Values are real annotation values (as per :attr: `Format.VALUE ` format)
134145 for defined values, and :class: `ForwardRef ` proxies for undefined
135146 values. Real objects may contain references to :class: `ForwardRef `
136147 proxy objects.
137148
138149 .. attribute :: STRING
139- :value: 3
150+ :value: 4
140151
141152 Values are the text string of the annotation as it appears in the
142153 source code, up to modifications including, but not restricted to,
143154 whitespace normalizations and constant values optimizations.
144155
145156 The exact values of these strings may change in future versions of Python.
146157
147- .. attribute :: VALUE_WITH_FAKE_GLOBALS
148- :value: 4
149-
150- Special value used to signal that an annotate function is being
151- evaluated in a special environment with fake globals. When passed this
152- value, annotate functions should either return the same value as for
153- the :attr: `Format.VALUE ` format, or raise :exc: `NotImplementedError `
154- to signal that they do not support execution in this environment.
155- This format is only used internally and should not be passed to
156- the functions in this module.
157-
158158 .. versionadded :: 3.14
159159
160160.. class :: ForwardRef
@@ -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