1# SPDX-License-Identifier: MIT

2

3

4 import inspect

5 import platform

6 import sys

7 import threading

8 import types

9 import warnings

10

11 from collections.abc import Mapping, Sequence # noqa

12

13

14 PYPY = platform.python_implementation() == "PyPy"

15 PY310 = sys.version_info[:2] >= (3, 10)

16 PY_3_12_PLUS = sys.version_info[:2] >= (3, 12)

17

18

19 def just_warn(*args, **kw):

20 warnings.warn(

21 "Running interpreter doesn't sufficiently support code object "

22 "introspection. Some features like bare super() or accessing "

23 "__class__ will not work with slotted classes.",

24 RuntimeWarning,

25 stacklevel=2,

26 )

27

28

29 class _AnnotationExtractor:

30 """

31 Extract type annotations from a callable, returning None whenever there

32 is none.

33 """

34

35 __slots__ = ["sig"]

36

37 def __init__(self, callable):

38 try:

39 self.sig = inspect.signature(callable)

40 except (ValueError, TypeError): # inspect failed

41 self.sig = None

42

43 def get_first_param_type(self):

44 """

45 Return the type annotation of the first argument if it's not empty.

46 """

47 if not self.sig:

48 return None

49

50 params = list(self.sig.parameters.values())

51 if params and params[0].annotation is not inspect.Parameter.empty:

52 return params[0].annotation

53

54 return None

55

56 def get_return_type(self):

57 """

58 Return the return type if it's not empty.

59 """

60 if (

61 self.sig

62 and self.sig.return_annotation is not inspect.Signature.empty

63 ):

64 return self.sig.return_annotation

65

66 return None

67

68

69 def make_set_closure_cell():

70 """Return a function of two arguments (cell, value) which sets

71 the value stored in the closure cell `cell` to `value`.

72 """

73 # pypy makes this easy. (It also supports the logic below, but

74 # why not do the easy/fast thing?)

75 if PYPY:

76

77 def set_closure_cell(cell, value):

78 cell.__setstate__((value,))

79

80 return set_closure_cell

81

82 # Otherwise gotta do it the hard way.

83

84 # Create a function that will set its first cellvar to `value`.

85 def set_first_cellvar_to(value):

86 x = value

87 return

88

89 # This function will be eliminated as dead code, but

90 # not before its reference to `x` forces `x` to be

91 # represented as a closure cell rather than a local.

92 def force_x_to_be_a_cell(): # pragma: no cover

93 return x

94

95 try:

96 # Extract the code object and make sure our assumptions about

97 # the closure behavior are correct.

98 co = set_first_cellvar_to.__code__

99 if co.co_cellvars != ("x",) or co.co_freevars != ():

100 raise AssertionError # pragma: no cover

101

102 # Convert this code object to a code object that sets the

103 # function's first _freevar_ (not cellvar) to the argument.

104 if sys.version_info >= (3, 8):

105

106 def set_closure_cell(cell, value):

107 cell.cell_contents = value

108

109 else:

110 args = [co.co_argcount]

111 args.append(co.co_kwonlyargcount)

112 args.extend(

113 [

114 co.co_nlocals,

115 co.co_stacksize,

116 co.co_flags,

117 co.co_code,

118 co.co_consts,

119 co.co_names,

120 co.co_varnames,

121 co.co_filename,

122 co.co_name,

123 co.co_firstlineno,

124 co.co_lnotab,

125 # These two arguments are reversed:

126 co.co_cellvars,

127 co.co_freevars,

128 ]

129 )

130 set_first_freevar_code = types.CodeType(*args)

131

132 def set_closure_cell(cell, value):

133 # Create a function using the set_first_freevar_code,

134 # whose first closure cell is `cell`. Calling it will

135 # change the value of that cell.

136 setter = types.FunctionType(

137 set_first_freevar_code, {}, "setter", (), (cell,)

138 )

139 # And call it to set the cell.

140 setter(value)

141

142 # Make sure it works on this interpreter:

143 def make_func_with_cell():

144 x = None

145

146 def func():

147 return x # pragma: no cover

148

149 return func

150

151 cell = make_func_with_cell().__closure__[0]

152 set_closure_cell(cell, 100)

153 if cell.cell_contents != 100:

154 raise AssertionError # pragma: no cover

155

156 except Exception:

157 return just_warn

158 else:

159 return set_closure_cell

160

161

162 set_closure_cell = make_set_closure_cell()

163

164# Thread-local global to track attrs instances which are already being repr'd.

165# This is needed because there is no other (thread-safe) way to pass info

166# about the instances that are already being repr'd through the call stack

167# in order to ensure we don't perform infinite recursion.

168#

169# For instance, if an instance contains a dict which contains that instance,

170# we need to know that we're already repr'ing the outside instance from within

171# the dict's repr() call.

172#

173# This lives here rather than in _make.py so that the functions in _make.py

174# don't have a direct reference to the thread-local in their globals dict.

175# If they have such a reference, it breaks cloudpickle.

176 repr_context = threading.local()