File: codescope.py

package info (click to toggle)
python-beartype 0.22.9-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 9,504 kB
  • sloc: python: 85,502; sh: 328; makefile: 30; javascript: 18
file content (660 lines) | stat: -rw-r--r-- 29,199 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
#!/usr/bin/env python3
# --------------------( LICENSE                            )--------------------
# Copyright (c) 2014-2025 Beartype authors.
# See "LICENSE" for further details.

'''
**Beartype decorator PEP-compliant code wrapper scope utilities** (i.e.,
functions handling the possibly nested lexical scopes enclosing wrapper
functions generated by the :func:`beartype.beartype` decorator).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO                               }....................
#FIXME: Hah-hah! Finally figured out how to do PEP-noncompliant recursive type
#hints... mostly. That said, since @beartype already supports PEP 695-compliant
#recursive type aliases, it's unclear whether any of this is desirable.
#Still, we specced it out. So, here it is. It's a two-parter consisting of:
#* *PART I.* In the first part:
#  * Refactor our code generation algorithm to additionally maintain a stack of
#    all parent type hints of the currently visited type hint. Note that we need
#    to do this anyway to support the __beartype_hint__() protocol. See "FIXME:"
#    comments in the "beartype.plug._plughintable" submodule pertaining to that
#    protocol for further details on properly building out this stack.
#  * When that algorithm visits a forward reference:
#    * That algorithm calls the express_func_scope_type_ref() function
#      generating type-checking code for that reference. Refactor that call to
#      additionally pass that stack of parent hints to that function.
#    * Refactor the express_func_scope_type_ref() function to:
#      * If the passed forward reference is relative, additionally return that
#        stack in the returned 3-tuple
#        "(forwardref_expr, refs_type_basename, forwardref_parent_hints)",
#        where "forwardref_parent_hints" is that stack.
#* *PART II.* In the second part:
#  * Refactor the beartype._decor._nontype._wrap.wrapmain._unmemoize_func_wrapper_code()
#    function to additionally:
#    * If the passed forward reference is relative *AND* the unqualified
#      basename of an existing attribute in a local or global scope of the
#      currently decorated callable *AND* the value of that attribute is a
#      parent type hint on the stack of parent type hints returned by the
#      previously called express_func_scope_type_ref() function, then
#      *THIS REFERENCE INDICATES A RECURSIVE TYPE HINT.* In this case:
#      * Replace this forward reference with a new recursive type-checking
#        "beartype._check.forward.reference.fwdrefabc.BeartypeForwardRef_{forwardref}"
#        subclass whose is_instance() tester method recursively calls itself
#        indefinitely. If doing so generates a "RecursionError", @beartype
#        considers that the user's problem. *wink*
#      * Note that this is_instance() tester method should guard itself against
#        recursion by accepting an optional "obj_ids: FrozenSetInts =
#        FROZEN_SET_EMPTY" parameter recording the IDs of all previously tested
#        objects. Consider infinite containers: e.g.,
#            infinite_list = []
#            infinite_list.append(infinite_list)
#
#Done and done. Phew!

# ....................{ IMPORTS                            }....................
from beartype.roar import BeartypeDecorHintNonpepException
from beartype.typing import (
    Dict,
    List,
    Optional,
    Tuple,
)
from beartype._cave._cavemap import NoneTypeOr
from beartype._check.forward.reference.fwdrefmake import (
    make_forwardref_indexable_subtype)
from beartype._check.forward.reference.fwdreftest import is_beartype_forwardref
from beartype._check.code.snip.codesnipstr import (
    CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_PREFIX,
    CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_SUFFIX,
)
from beartype._data.cls.datacls import TYPES_SET_OR_TUPLE
from beartype._data.typing.datatyping import (
    LexicalScope,
    Pep484585ForwardRef,
    SetOrTupleTypes,
    TypeOrSetOrTupleTypes,
    TupleTypes,
)
from beartype._util.cls.pep.clspep3119 import (
    die_unless_type_isinstanceable,
    die_unless_object_isinstanceable,
)
from beartype._util.cls.utilclstest import is_type_builtin
from beartype._util.func.utilfuncscope import add_func_scope_attr
from beartype._util.hint.pep.proposal.pep484585.pep484585ref import (
    get_hint_pep484585_ref_names)
from beartype._util.utilobject import get_object_type_basename
from collections.abc import Set

# ....................{ ADDERS ~ type                      }....................
#FIXME: Unit test us up, please.
def add_func_scope_ref(
    # Mandatory parameters.
    func_scope: LexicalScope,
    ref_module_name: Optional[str],
    ref_name: str,

    # Optional parameters.
    exception_prefix: str = 'Globally or locally scoped forward reference ',
) -> str:
    '''
    Add a new **scoped forward reference proxy** (i.e., new key-value pair of
    the passed dictionary mapping from the name to value of each globally or
    locally scoped attribute externally accessed elsewhere, whose key is a
    machine-readable name internally generated by this function to uniquely
    refer to a new forward reference proxy proxying the class with the passed
    attribute name residing in the module with the passed module name) to the
    passed scope *and* return that name.

    Parameters
    ----------
    func_scope : LexicalScope
        Local or global scope to add this class or tuple of classes to.
    ref_module_name : Optional[str]
        Possibly undefined fully-qualified module name referred to by this
        forward reference.
    ref_name : str
        Possibly unqualified classname referred to by this forward reference.
    exception_prefix : str, optional
        Human-readable label prefixing the representation of this object in the
        exception message. Defaults to a sensible string.

    Returns
    -------
    str
        Name of this forward reference proxy in this scope generated by this
        function.

    Raises
    ------
    _BeartypeUtilCallableException
        If an attribute with the same name as that internally generated by this
        adder but having a different value already exists in this scope. This
        adder uniquifies names by object identifier and should thus *never*
        generate name collisions. This exception is thus intentionally raised
        as a private rather than public exception.
    '''

    # Forward reference proxy referring to this class.
    hint_ref = make_forwardref_indexable_subtype(ref_module_name, ref_name)

    # Name of a new parameter passing this forward reference proxy.
    hint_ref_arg_name = add_func_scope_attr(
        func_scope=func_scope, attr=hint_ref)

    # Return this name.
    return hint_ref_arg_name

# ....................{ ADDERS ~ type                      }....................
#FIXME: Unit test us up, please.
def add_func_scope_type_or_types(
    # Mandatory parameters.
    func_scope: LexicalScope,
    type_or_types: TypeOrSetOrTupleTypes,

    # Optional parameters.
    exception_prefix: str = (
        'Globally or locally scoped class or tuple of classes '),
) -> str:
    '''
    Add a new **scoped class or tuple of classes** (i.e., new key-value pair of
    the passed dictionary mapping from the name to value of each globally or
    locally scoped attribute externally accessed elsewhere, whose key is a
    machine-readable name internally generated by this function to uniquely
    refer to the passed class or tuple of classes and whose value is that class
    or tuple) to the passed scope *and* return that name.

    This function additionally caches this tuple with the beartypistry
    singleton to reduce space consumption for tuples duplicated across the
    active Python interpreter.

    Parameters
    ----------
    func_scope : LexicalScope
        Local or global scope to add this class or tuple of classes to.
    type_or_types : TypeOrSetOrTupleTypes
        Classes to be added to this scope, defined as either:

        * A single class.
        * A set of one or more classes.
        * A tuple of one or more classes.
    exception_prefix : str, optional
        Human-readable label prefixing the representation of this object in the
        exception message. Defaults to a sensible string.

    Returns
    -------
    str
        Name of this class or tuple in this scope generated by this function.

    Raises
    ------
    BeartypeDecorHintNonpepException
        If this hint is either:

        * Neither a class nor tuple.
        * A tuple that is empty.
    BeartypeDecorHintPep3119Exception
        If hint is:

        * A class that is *not* isinstanceable (i.e., passable as the second
          argument to the :func:`isinstance` builtin).
        * A tuple of one or more items that are *not* isinstanceable classes.
    _BeartypeUtilCallableException
        If an attribute with the same name as that internally generated by this
        adder but having a different value already exists in this scope. This
        adder uniquifies names by object identifier and should thus *never*
        generate name collisions. This exception is thus intentionally raised
        as a private rather than public exception.
    '''

    # Return either...
    return (
        # If this hint is a class, the name of a new parameter passing this
        # class;
        add_func_scope_type(
            func_scope=func_scope,
            cls=type_or_types,
            exception_prefix=exception_prefix,
        )
        if isinstance(type_or_types, type) else
        # Else, this hint is *NOT* a class. In this case:
        # * If this hint is a tuple of classes, the name of a new parameter
        #   passing this tuple.
        # * Else, raise an exception.
        add_func_scope_types(
            func_scope=func_scope,
            types=type_or_types,
            exception_prefix=exception_prefix,
        )
    )


def add_func_scope_type(
    # Mandatory parameters.
    func_scope: LexicalScope,
    cls: type,

    # Optional parameters.
    exception_prefix: str = 'Globally or locally scoped class ',
) -> str:
    '''
    Add a new **scoped class** (i.e., new key-value pair of the passed
    dictionary mapping from the name to value of each globally or locally scoped
    attribute externally accessed elsewhere, whose key is a machine-readable
    name internally generated by this function to uniquely refer to the passed
    class and whose value is that class) to the passed scope *and* return that
    name.

    Parameters
    ----------
    func_scope : LexicalScope
        Local or global scope to add this class to.
    cls : type
        Arbitrary class to be added to this scope.
    exception_prefix : str, optional
        Human-readable label prefixing the representation of this object in the
        exception message. Defaults to a sensible string.

    Returns
    -------
    str
        Name of this class in this scope generated by this function.

    Raises
    ------
    BeartypeDecorHintPep3119Exception
        If this class is *not* isinstanceable (i.e., passable as the second
        argument to the :func:`isinstance` builtin).
    _BeartypeUtilCallableException
        If an attribute with the same name as that internally generated by this
        adder but having a different value already exists in this scope. This
        adder uniquifies names by object identifier and should thus *never*
        generate name collisions. This exception is thus intentionally raised
        as a private rather than public exception.
    '''

    # If this object is *NOT* an isinstanceable class, raise an exception.
    die_unless_type_isinstanceable(cls=cls, exception_prefix=exception_prefix)
    # Else, this object is an isinstanceable class.

    # Return either...
    return (
        # If this type is a builtin (i.e., globally accessible C-based type
        # requiring *no* explicit importation), the unqualified basename of
        # this type as is, as this type requires no parametrization;
        get_object_type_basename(cls)
        if is_type_builtin(cls) else
        # Else, the name of a new parameter passing this class.
        add_func_scope_attr(
            func_scope=func_scope, attr=cls, exception_prefix=exception_prefix)
    )


def add_func_scope_types(
    # Mandatory parameters.
    func_scope: LexicalScope,
    types: SetOrTupleTypes,

    # Optional parameters.
    is_unique: Optional[bool] = None,
    exception_prefix: str = (
        'Globally or locally scoped set or tuple of classes '),
) -> str:
    '''
    Add a new **scoped tuple of classes** (i.e., new key-value pair of the
    passed dictionary mapping from the name to value of each globally or locally
    scoped attribute externally accessed elsewhere, whose key is a
    machine-readable name internally generated by this function to uniquely
    refer to the passed set or tuple of classes and whose value is that tuple)
    to the passed scope *and* return that machine-readable name.

    This function additionally caches this tuple with the
    :data:`._tuple_union_to_tuple_union` dictionary to reduce space consumption
    for tuples duplicated across the active Python interpreter.

    Parameters
    ----------
    func_scope : LexicalScope
        Local or global scope to add this object to.
    types : SetOrTupleOfTypes
        Set or tuple of arbitrary types to be added to this scope.
    is_unique : Optional[bool]
        Tri-state boolean governing whether this function attempts to
        deduplicate types in the ``types`` iterable. Specifically, either:

        * :data:`True`, in which case the caller guarantees ``types`` to contain
          *no* duplicate types.
        * :data:`False`, in which case this function assumes ``types`` to
          contain duplicate types by internally (in order):

          #. Coercing this tuple into a set, thus implicitly ignoring both
             duplicates and ordering of types in this tuple.
          #. Coercing that set back into another tuple.
          #. If these two tuples differ, the passed tuple contains one or more
             duplicates; in this case, the duplicate-free tuple is cached and
             passed.
          #. Else, the passed tuple contains no duplicates; in this case, the
             passed tuple is cached and passed.

        * :data:`None`, in which case this function reduces this parameter to
          either:

          * :data:`True` if ``types`` is a :class:`tuple`.
          * :data:`False` if ``types`` is a :class:`set`.

        This tri-state boolean does *not* simply enable an edge-case
        optimization, though it certainly does that; this boolean enables
        callers to guarantee that this function caches and passes the passed
        tuple rather than a new tuple internally created by this function.

        Defaults to :data:`None`.
    exception_prefix : str, optional
        Human-readable label prefixing the representation of this object in the
        exception message. Defaults to a sensible string.

    Returns
    -------
    str
        Name of this tuple in this scope generated by this function.

    Raises
    ------
    BeartypeDecorHintNonpepException
        If this hint is either:

        * Neither a set nor tuple.
        * A set or tuple that is empty.
    BeartypeDecorHintPep3119Exception
        If one or more items of this hint are *not* isinstanceable classes
        (i.e., classes passable as the second argument to the
        :func:`isinstance` builtin).
    _BeartypeUtilCallableException
        If an attribute with the same name as that internally generated by this
        adder but having a different value already exists in this scope. This
        adder uniquifies names by object identifier and should thus *never*
        generate name collisions. This exception is thus intentionally raised
        as a private rather than public exception.
    '''
    assert isinstance(is_unique, NoneTypeOr[bool]), (
        f'{repr(is_unique)} neither bool nor "None".')

    # ....................{ VALIDATE                       }....................
    # If this container is neither a set nor tuple, raise an exception.
    if not isinstance(types, TYPES_SET_OR_TUPLE):
        raise BeartypeDecorHintNonpepException(
            f'{exception_prefix}{repr(types)} neither set nor tuple.')
    # Else, this container is either a set or tuple.
    #
    # If this container is empty, raise an exception.
    elif not types:
        raise BeartypeDecorHintNonpepException(f'{exception_prefix}empty.')
    # Else, this container is non-empty.
    #
    # If this container only contains one type, register only this type.
    elif len(types) == 1:
        return add_func_scope_type(
            # The first and only item of this container, accessed as either:
            # * If this container is a tuple, that item with fast indexing.
            # * If this container is a set, that item with slow iteration.
            cls=types[0] if isinstance(types, tuple) else next(iter(types)),
            func_scope=func_scope,
            exception_prefix=exception_prefix,
        )
    # Else, this container either contains two or more types.

    # If the caller did *NOT* explicitly pass the "is_unique" parameter, default
    # this parameter to true *ONLY* if this container is a set.
    if is_unique is None:
        is_unique = isinstance(types, set)
    # Else, the caller explicitly passed the "is_unique" parameter.
    #
    # In either case, "is_unique" is now a proper bool.
    assert isinstance(is_unique, bool)

    # ....................{ FORWARDREF                     }....................
    # True only if this container contains one or more beartype-specific forward
    # reference proxies. Although these proxies are technically isinstanceable
    # classes, attempting to pass these proxies as the second parameter to the
    # isinstance() builtin also raises exceptions when the underlying
    # user-defined classes proxied by these proxies have yet to be declared.
    # Since these proxies are thus *MUCH* more fragile than standard classes, we
    # reduce the likelihood of exceptions by deprioritizing these proxies in
    # this container (i.e., moving these proxies to the end of this container).
    is_types_ref = False

    # For each type in this container...
    for cls in types:
        # If this type is a beartype-specific forward reference proxy...
        if is_beartype_forwardref(cls):
            # print(f'Found forward reference proxy {repr(cls)}...')
            # Note that this container contains at least one such proxy.
            is_types_ref = True

            # Halt iteration.
            break

    # If this container contains at least one such proxy...
    if is_types_ref:
        # List of all such proxies in this container.
        #
        # Note that we intentionally avoid instantiating this pair of lists
        # above in the common case that this container contains no such proxies.
        types_ref: List[type] = []

        # List of all other types in this container (i.e., normal types that are
        # *NOT* beartype-specific forward reference proxies).
        types_nonref: List[type] = []

        # For each type in this container...
        for cls in types:
            # If this type is such a proxy, append this proxy to the list of all
            # such proxies.
            if is_beartype_forwardref(cls):
                types_ref.append(cls)
            # Else, this type is *NOT* such a proxy. In this case...
            else:
                # print(f'Appending non-forward reference proxy {repr(cls)}...')

                # If this non-proxy is *NOT* an isinstanceable class, raise an
                # exception.
                #
                # Note that the companion "types_ref" tuple is intentionally
                # *NOT* validated above. Why? Because doing so would prematurely
                # invoke the __instancecheck__() dunder method on the metaclass
                # of the proxies in that tuple, which would then erroneously
                # attempt to resolve the possibly undefined types to which those
                # proxies refer. Instead, simply accept that tuple of proxies as
                # is for now and defer validating those proxies for later.
                die_unless_type_isinstanceable(
                    cls=cls, exception_prefix=exception_prefix)

                # Append this proxy to the list of all non-proxy types
                types_nonref.append(cls)

        # If the caller guaranteed these tuples to be duplicate-free,
        # efficiently concatenate these lists into a tuple such that all
        # non-proxy types appear *BEFORE* all proxy types.
        if is_unique:
            types = tuple(types_nonref + types_ref)
        # Else, the caller failed to guarantee these tuples to be
        # duplicate-free. In this case, coerce these tuples into (in order):
        # * Sets, thus ignoring duplicates and ordering.
        # * Back into duplicate-free tuples.
        else:
            types = tuple(set(types_nonref)) + tuple(set(types_ref))
        # Else, the caller guaranteed these tuples to be duplicate-free.
    # Else, this container contains *NO* such proxies. In this case, preserve
    # the ordering of items in this container as is.
    else:
        # If this container is a set, coerce this frozenset into a tuple.
        if isinstance(types, Set):
            types = tuple(types)
        # Else, this container is *NOT* a set. By elimination, this container
        # should now be a tuple.
        #
        # In either case, this container should now be a tuple.

        # If this container is *NOT* a tuple or is a tuple containing one or
        # more items that are *NOT* isinstanceable classes, raise an exception.
        die_unless_object_isinstanceable(
            obj=types, exception_prefix=exception_prefix)
        # Else, this container is a tuple of only isinstanceable classes.

        # If the caller failed to guarantee this tuple to be duplicate-free,
        # coerce this tuple into (in order):
        # * A set, thus ignoring duplicates and ordering.
        # * Back into a duplicate-free tuple.
        if not is_unique:
            # print(f'Uniquifying type tuple {repr(types)} to...')
            types = tuple(set(types))
            # print(f'...uniquified type tuple {repr(types)}.')
        # Else, the caller guaranteed this tuple to be duplicate-free.

    # In either case, this container is now guaranteed to be a tuple containing
    # only duplicate-free classes.
    assert isinstance(types, tuple), (
        f'{exception_prefix}{repr(types)} not tuple.')

    # ....................{ CACHE                          }....................
    # If this tuple has *NOT* already been cached, do so.
    if types not in _tuple_union_to_tuple_union:
        _tuple_union_to_tuple_union[types] = types
    # Else, this tuple has already been cached. In this case, deduplicate this
    # tuple by reusing the previously cached tuple.
    else:
        types = _tuple_union_to_tuple_union[types]

    # ....................{ RETURN                         }....................
    # Return the name of a new parameter passing this tuple.
    return add_func_scope_attr(
        attr=types, func_scope=func_scope, exception_prefix=exception_prefix)

# ....................{ EXPRESSERS ~ type                  }....................
def express_func_scope_type_ref(
    # Mandatory parameters.
    func_scope: LexicalScope,
    forwardref: Pep484585ForwardRef,
    refs_type_basename: Optional[set],

    # Optional parameters.
    exception_prefix: str = 'Globally or locally scoped forward reference ',
) -> Tuple[str, Optional[set]]:
    '''
    Express the passed :pep:`484`- or :pep:`585`-compliant **forward reference**
    (i.e., fully-qualified or unqualified name of an arbitrary class that
    typically has yet to be declared) as a Python expression evaluating to this
    forward reference when accessed via the beartypistry singleton added as a
    new key-value pair of the passed dictionary, whose key is the string
    :attr:`beartype._data.code.datacodename.ARG_NAME_TYPISTRY` and whose value is the
    beartypistry singleton.

    Parameters
    ----------
    func_scope : LexicalScope
        Local or global scope to add this forward reference to.
    forwardref : Pep484585ForwardRef
        Forward reference to be expressed relative to this scope.
    refs_type_basename : Optional[set]
        Set of all existing **relative forward references** (i.e., unqualified
        basenames of all types referred to by all relative forward references
        relative to this scope) if any *or* :data:`None` otherwise (i.e., if no
        relative forward references have been expressed relative to this scope).
    exception_prefix : str, optional
        Human-readable substring prefixing raised exception messages. Defaults
        to a sensible string.

    Returns
    -------
    Tuple[str, Optional[set]]
        2-tuple ``(forwardref_expr, refs_type_basename)``, where:

        * ``forwardref_expr`` is the Python expression evaluating to this
          forward reference when accessed via the beartypistry singleton added
          to this scope.
        * ``refs_type_basename`` is either:

          * If this forward reference is a fully-qualified classname, the
            passed ``refs_type_basename`` set as is.
          * If this forward reference is an unqualified classname, either:

            * If the passed ``refs_type_basename`` set is *not* :data:`None`,
              this set with this classname added to it.
            * Else, a new set containing only this classname.

    Raises
    ------
    BeartypeDecorHintForwardRefException
        If this forward reference is *not* actually a forward reference.
    '''

    # Possibly undefined fully-qualified module name and possibly unqualified
    # classname referred to by this forward reference.
    ref_module_name, ref_name = get_hint_pep484585_ref_names(
        hint=forwardref, exception_prefix=exception_prefix)

    # If either...
    if (
        # This reference was instantiated with a module name...
        ref_module_name or
        # This classname contains one or more "." characters and is thus already
        # (...hopefully) fully-qualified...
        '.' in ref_name
    # Then this classname is either absolute *OR* relative to some module. In
    # either case, the class referred to by this reference can now be
    # dynamically imported at a later time. In this case...
    ):
        # Name of the hidden parameter providing this forward reference
        # proxy to be passed to this wrapper function.
        ref_expr = add_func_scope_ref(
            func_scope=func_scope,
            ref_module_name=ref_module_name,
            ref_name=ref_name,
            exception_prefix=exception_prefix,
        )
    # Else, this classname is unqualified. In this case...
    else:
        assert isinstance(refs_type_basename, NoneTypeOr[set]), (
            f'{repr(refs_type_basename)} neither set nor "None".')

        # If this set of unqualified classnames referred to by all relative
        # forward references has yet to be instantiated, do so.
        if refs_type_basename is None:
            refs_type_basename = set()
        # In any case, this set now exists.

        # Add this unqualified classname to this set.
        refs_type_basename.add(ref_name)

        # Placeholder substring to be replaced by the caller with a Python
        # expression evaluating to this unqualified classname canonicalized
        # relative to the module declaring the currently decorated callable
        # when accessed via the private "__beartypistry" parameter.
        ref_expr = (
            f'{CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_PREFIX}'
            f'{ref_name}'
            f'{CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_SUFFIX}'
        )

    # Return a 2-tuple of this expression and set of unqualified classnames.
    return (ref_expr, refs_type_basename)

# ....................{ PRIVATE ~ globals                  }....................
_tuple_union_to_tuple_union: Dict[TupleTypes, TupleTypes] = {}
'''
**Tuple union cache** (i.e., dictionary mapping from each tuple union passed to
the :func:`.add_func_scope_types` adder to that same union, preventing tuple
unions from being duplicated across calls to that adder).

This cache serves a dual purpose. Notably, this cache both enables:

* External callers to iterate over all previously instantiated forward reference
  proxies. This is particularly useful when responding to module reloading,
  which requires that *all* previously cached types be uncached.
* A minor reduction in space complexity by de-duplicating duplicating tuple
  unions. Since the existing ``callable_cached`` decorator could trivially do so
  as well, however, this is only a negligible side effect.
'''