@@ -485,3 +485,117 @@ annotations from the class and puts them in a separate attribute:
485
485
typ.classvars = classvars # Store the ClassVars in a separate attribute
486
486
return typ
487
487
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