Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jedi-0.18.2-py3.8.egg/jedi/inference/compiled/mixed.py: 30%

163 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:56 +0000

1""" 

2Used only for REPL Completion. 

3""" 

4 

5import inspect 

6from pathlib import Path 

7 

8from jedi.parser_utils import get_cached_code_lines 

9 

10from jedi import settings 

11from jedi.cache import memoize_method 

12from jedi.inference import compiled 

13from jedi.file_io import FileIO 

14from jedi.inference.names import NameWrapper 

15from jedi.inference.base_value import ValueSet, ValueWrapper, NO_VALUES 

16from jedi.inference.value import ModuleValue 

17from jedi.inference.cache import inference_state_function_cache, \ 

18 inference_state_method_cache 

19from jedi.inference.compiled.access import ALLOWED_GETITEM_TYPES, get_api_type 

20from jedi.inference.gradual.conversion import to_stub 

21from jedi.inference.context import CompiledContext, CompiledModuleContext, \ 

22 TreeContextMixin 

23 

24_sentinel = object() 

25 

26 

27class MixedObject(ValueWrapper): 

28 """ 

29 A ``MixedObject`` is used in two ways: 

30 

31 1. It uses the default logic of ``parser.python.tree`` objects, 

32 2. except for getattr calls and signatures. The names dicts are generated 

33 in a fashion like ``CompiledValue``. 

34 

35 This combined logic makes it possible to provide more powerful REPL 

36 completion. It allows side effects that are not noticable with the default 

37 parser structure to still be completable. 

38 

39 The biggest difference from CompiledValue to MixedObject is that we are 

40 generally dealing with Python code and not with C code. This will generate 

41 fewer special cases, because we in Python you don't have the same freedoms 

42 to modify the runtime. 

43 """ 

44 def __init__(self, compiled_value, tree_value): 

45 super().__init__(tree_value) 

46 self.compiled_value = compiled_value 

47 self.access_handle = compiled_value.access_handle 

48 

49 def get_filters(self, *args, **kwargs): 

50 yield MixedObjectFilter( 

51 self.inference_state, self.compiled_value, self._wrapped_value) 

52 

53 def get_signatures(self): 

54 # Prefer `inspect.signature` over somehow analyzing Python code. It 

55 # should be very precise, especially for stuff like `partial`. 

56 return self.compiled_value.get_signatures() 

57 

58 @inference_state_method_cache(default=NO_VALUES) 

59 def py__call__(self, arguments): 

60 # Fallback to the wrapped value if to stub returns no values. 

61 values = to_stub(self._wrapped_value) 

62 if not values: 

63 values = self._wrapped_value 

64 return values.py__call__(arguments) 

65 

66 def get_safe_value(self, default=_sentinel): 

67 if default is _sentinel: 

68 return self.compiled_value.get_safe_value() 

69 else: 

70 return self.compiled_value.get_safe_value(default) 

71 

72 @property 

73 def array_type(self): 

74 return self.compiled_value.array_type 

75 

76 def get_key_values(self): 

77 return self.compiled_value.get_key_values() 

78 

79 def py__simple_getitem__(self, index): 

80 python_object = self.compiled_value.access_handle.access._obj 

81 if type(python_object) in ALLOWED_GETITEM_TYPES: 

82 return self.compiled_value.py__simple_getitem__(index) 

83 return self._wrapped_value.py__simple_getitem__(index) 

84 

85 def negate(self): 

86 return self.compiled_value.negate() 

87 

88 def _as_context(self): 

89 if self.parent_context is None: 

90 return MixedModuleContext(self) 

91 return MixedContext(self) 

92 

93 def __repr__(self): 

94 return '<%s: %s; %s>' % ( 

95 type(self).__name__, 

96 self.access_handle.get_repr(), 

97 self._wrapped_value, 

98 ) 

99 

100 

101class MixedContext(CompiledContext, TreeContextMixin): 

102 @property 

103 def compiled_value(self): 

104 return self._value.compiled_value 

105 

106 

107class MixedModuleContext(CompiledModuleContext, MixedContext): 

108 pass 

109 

110 

111class MixedName(NameWrapper): 

112 """ 

113 The ``CompiledName._compiled_value`` is our MixedObject. 

114 """ 

115 def __init__(self, wrapped_name, parent_tree_value): 

116 super().__init__(wrapped_name) 

117 self._parent_tree_value = parent_tree_value 

118 

119 @property 

120 def start_pos(self): 

121 values = list(self.infer()) 

122 if not values: 

123 # This means a start_pos that doesn't exist (compiled objects). 

124 return 0, 0 

125 return values[0].name.start_pos 

126 

127 @memoize_method 

128 def infer(self): 

129 compiled_value = self._wrapped_name.infer_compiled_value() 

130 tree_value = self._parent_tree_value 

131 if tree_value.is_instance() or tree_value.is_class(): 

132 tree_values = tree_value.py__getattribute__(self.string_name) 

133 if compiled_value.is_function(): 

134 return ValueSet({MixedObject(compiled_value, v) for v in tree_values}) 

135 

136 module_context = tree_value.get_root_context() 

137 return _create(self._inference_state, compiled_value, module_context) 

138 

139 

140class MixedObjectFilter(compiled.CompiledValueFilter): 

141 def __init__(self, inference_state, compiled_value, tree_value): 

142 super().__init__(inference_state, compiled_value) 

143 self._tree_value = tree_value 

144 

145 def _create_name(self, name): 

146 return MixedName( 

147 super()._create_name(name), 

148 self._tree_value, 

149 ) 

150 

151 

152@inference_state_function_cache() 

153def _load_module(inference_state, path): 

154 return inference_state.parse( 

155 path=path, 

156 cache=True, 

157 diff_cache=settings.fast_parser, 

158 cache_path=settings.cache_directory 

159 ).get_root_node() 

160 

161 

162def _get_object_to_check(python_object): 

163 """Check if inspect.getfile has a chance to find the source.""" 

164 try: 

165 python_object = inspect.unwrap(python_object) 

166 except ValueError: 

167 # Can return a ValueError when it wraps around 

168 pass 

169 

170 if (inspect.ismodule(python_object) 

171 or inspect.isclass(python_object) 

172 or inspect.ismethod(python_object) 

173 or inspect.isfunction(python_object) 

174 or inspect.istraceback(python_object) 

175 or inspect.isframe(python_object) 

176 or inspect.iscode(python_object)): 

177 return python_object 

178 

179 try: 

180 return python_object.__class__ 

181 except AttributeError: 

182 raise TypeError # Prevents computation of `repr` within inspect. 

183 

184 

185def _find_syntax_node_name(inference_state, python_object): 

186 original_object = python_object 

187 try: 

188 python_object = _get_object_to_check(python_object) 

189 path = inspect.getsourcefile(python_object) 

190 except (OSError, TypeError): 

191 # The type might not be known (e.g. class_with_dict.__weakref__) 

192 return None 

193 path = None if path is None else Path(path) 

194 try: 

195 if path is None or not path.exists(): 

196 # The path might not exist or be e.g. <stdin>. 

197 return None 

198 except OSError: 

199 # Might raise an OSError on Windows: 

200 # 

201 # [WinError 123] The filename, directory name, or volume label 

202 # syntax is incorrect: '<string>' 

203 return None 

204 

205 file_io = FileIO(path) 

206 module_node = _load_module(inference_state, path) 

207 

208 if inspect.ismodule(python_object): 

209 # We don't need to check names for modules, because there's not really 

210 # a way to write a module in a module in Python (and also __name__ can 

211 # be something like ``email.utils``). 

212 code_lines = get_cached_code_lines(inference_state.grammar, path) 

213 return module_node, module_node, file_io, code_lines 

214 

215 try: 

216 name_str = python_object.__name__ 

217 except AttributeError: 

218 # Stuff like python_function.__code__. 

219 return None 

220 

221 if name_str == '<lambda>': 

222 return None # It's too hard to find lambdas. 

223 

224 # Doesn't always work (e.g. os.stat_result) 

225 names = module_node.get_used_names().get(name_str, []) 

226 # Only functions and classes are relevant. If a name e.g. points to an 

227 # import, it's probably a builtin (like collections.deque) and needs to be 

228 # ignored. 

229 names = [ 

230 n for n in names 

231 if n.parent.type in ('funcdef', 'classdef') and n.parent.name == n 

232 ] 

233 if not names: 

234 return None 

235 

236 try: 

237 code = python_object.__code__ 

238 # By using the line number of a code object we make the lookup in a 

239 # file pretty easy. There's still a possibility of people defining 

240 # stuff like ``a = 3; foo(a); a = 4`` on the same line, but if people 

241 # do so we just don't care. 

242 line_nr = code.co_firstlineno 

243 except AttributeError: 

244 pass 

245 else: 

246 line_names = [name for name in names if name.start_pos[0] == line_nr] 

247 # There's a chance that the object is not available anymore, because 

248 # the code has changed in the background. 

249 if line_names: 

250 names = line_names 

251 

252 code_lines = get_cached_code_lines(inference_state.grammar, path) 

253 # It's really hard to actually get the right definition, here as a last 

254 # resort we just return the last one. This chance might lead to odd 

255 # completions at some points but will lead to mostly correct type 

256 # inference, because people tend to define a public name in a module only 

257 # once. 

258 tree_node = names[-1].parent 

259 if tree_node.type == 'funcdef' and get_api_type(original_object) == 'instance': 

260 # If an instance is given and we're landing on a function (e.g. 

261 # partial in 3.5), something is completely wrong and we should not 

262 # return that. 

263 return None 

264 return module_node, tree_node, file_io, code_lines 

265 

266 

267@inference_state_function_cache() 

268def _create(inference_state, compiled_value, module_context): 

269 # TODO accessing this is bad, but it probably doesn't matter that much, 

270 # because we're working with interpreters only here. 

271 python_object = compiled_value.access_handle.access._obj 

272 result = _find_syntax_node_name(inference_state, python_object) 

273 if result is None: 

274 # TODO Care about generics from stuff like `[1]` and don't return like this. 

275 if type(python_object) in (dict, list, tuple): 

276 return ValueSet({compiled_value}) 

277 

278 tree_values = to_stub(compiled_value) 

279 if not tree_values: 

280 return ValueSet({compiled_value}) 

281 else: 

282 module_node, tree_node, file_io, code_lines = result 

283 

284 if module_context is None or module_context.tree_node != module_node: 

285 root_compiled_value = compiled_value.get_root_context().get_value() 

286 # TODO this __name__ might be wrong. 

287 name = root_compiled_value.py__name__() 

288 string_names = tuple(name.split('.')) 

289 module_value = ModuleValue( 

290 inference_state, module_node, 

291 file_io=file_io, 

292 string_names=string_names, 

293 code_lines=code_lines, 

294 is_package=root_compiled_value.is_package(), 

295 ) 

296 if name is not None: 

297 inference_state.module_cache.add(string_names, ValueSet([module_value])) 

298 module_context = module_value.as_context() 

299 

300 tree_values = ValueSet({module_context.create_value(tree_node)}) 

301 if tree_node.type == 'classdef': 

302 if not compiled_value.is_class(): 

303 # Is an instance, not a class. 

304 tree_values = tree_values.execute_with_values() 

305 

306 return ValueSet( 

307 MixedObject(compiled_value, tree_value=tree_value) 

308 for tree_value in tree_values 

309 )