]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | """ |
2 | Used only for REPL Completion. | |
3 | """ | |
4 | ||
5 | import inspect | |
6 | from pathlib import Path | |
7 | ||
8 | from jedi.parser_utils import get_cached_code_lines | |
9 | ||
10 | from jedi import settings | |
11 | from jedi.cache import memoize_method | |
12 | from jedi.inference import compiled | |
13 | from jedi.file_io import FileIO | |
14 | from jedi.inference.names import NameWrapper | |
15 | from jedi.inference.base_value import ValueSet, ValueWrapper, NO_VALUES | |
16 | from jedi.inference.value import ModuleValue | |
17 | from jedi.inference.cache import inference_state_function_cache, \ | |
18 | inference_state_method_cache | |
19 | from jedi.inference.compiled.access import ALLOWED_GETITEM_TYPES, get_api_type | |
20 | from jedi.inference.gradual.conversion import to_stub | |
21 | from jedi.inference.context import CompiledContext, CompiledModuleContext, \ | |
22 | TreeContextMixin | |
23 | ||
24 | _sentinel = object() | |
25 | ||
26 | ||
27 | class 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 | ||
101 | class MixedContext(CompiledContext, TreeContextMixin): | |
102 | @property | |
103 | def compiled_value(self): | |
104 | return self._value.compiled_value | |
105 | ||
106 | ||
107 | class MixedModuleContext(CompiledModuleContext, MixedContext): | |
108 | pass | |
109 | ||
110 | ||
111 | class 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 | ||
140 | class 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, *args, **kwargs): | |
146 | return MixedName( | |
147 | super()._create_name(*args, **kwargs), | |
148 | self._tree_value, | |
149 | ) | |
150 | ||
151 | ||
152 | @inference_state_function_cache() | |
153 | def _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 | ||
162 | def _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 | ||
185 | def _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() | |
268 | def _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 | ) |