]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | """ |
2 | Like described in the :mod:`parso.python.tree` module, | |
3 | there's a need for an ast like module to represent the states of parsed | |
4 | modules. | |
5 | ||
6 | But now there are also structures in Python that need a little bit more than | |
7 | that. An ``Instance`` for example is only a ``Class`` before it is | |
8 | instantiated. This class represents these cases. | |
9 | ||
10 | So, why is there also a ``Class`` class here? Well, there are decorators and | |
11 | they change classes in Python 3. | |
12 | ||
13 | Representation modules also define "magic methods". Those methods look like | |
14 | ``py__foo__`` and are typically mappable to the Python equivalents ``__call__`` | |
15 | and others. Here's a list: | |
16 | ||
17 | ====================================== ======================================== | |
18 | **Method** **Description** | |
19 | -------------------------------------- ---------------------------------------- | |
20 | py__call__(arguments: Array) On callable objects, returns types. | |
21 | py__bool__() Returns True/False/None; None means that | |
22 | there's no certainty. | |
23 | py__bases__() Returns a list of base classes. | |
24 | py__iter__() Returns a generator of a set of types. | |
25 | py__class__() Returns the class of an instance. | |
26 | py__simple_getitem__(index: int/str) Returns a a set of types of the index. | |
27 | Can raise an IndexError/KeyError. | |
28 | py__getitem__(indexes: ValueSet) Returns a a set of types of the index. | |
29 | py__file__() Only on modules. Returns None if does | |
30 | not exist. | |
31 | py__package__() -> List[str] Only on modules. For the import system. | |
32 | py__path__() Only on modules. For the import system. | |
33 | py__get__(call_object) Only on instances. Simulates | |
34 | descriptors. | |
35 | py__doc__() Returns the docstring for a value. | |
36 | ====================================== ======================================== | |
37 | ||
38 | """ | |
39 | from jedi import debug | |
40 | from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted, \ | |
41 | function_is_property | |
42 | from jedi.inference.cache import inference_state_method_cache, CachedMetaClass, \ | |
43 | inference_state_method_generator_cache | |
44 | from jedi.inference import compiled | |
45 | from jedi.inference.lazy_value import LazyKnownValues, LazyTreeValue | |
46 | from jedi.inference.filters import ParserTreeFilter | |
47 | from jedi.inference.names import TreeNameDefinition, ValueName | |
48 | from jedi.inference.arguments import unpack_arglist, ValuesArguments | |
49 | from jedi.inference.base_value import ValueSet, iterator_to_value_set, \ | |
50 | NO_VALUES | |
51 | from jedi.inference.context import ClassContext | |
52 | from jedi.inference.value.function import FunctionAndClassBase | |
53 | from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager | |
54 | from jedi.plugins import plugin_manager | |
55 | ||
56 | ||
57 | class ClassName(TreeNameDefinition): | |
58 | def __init__(self, class_value, tree_name, name_context, apply_decorators): | |
59 | super().__init__(name_context, tree_name) | |
60 | self._apply_decorators = apply_decorators | |
61 | self._class_value = class_value | |
62 | ||
63 | @iterator_to_value_set | |
64 | def infer(self): | |
65 | # We're using a different value to infer, so we cannot call super(). | |
66 | from jedi.inference.syntax_tree import tree_name_to_values | |
67 | inferred = tree_name_to_values( | |
68 | self.parent_context.inference_state, self.parent_context, self.tree_name) | |
69 | ||
70 | for result_value in inferred: | |
71 | if self._apply_decorators: | |
72 | yield from result_value.py__get__(instance=None, class_value=self._class_value) | |
73 | else: | |
74 | yield result_value | |
75 | ||
76 | @property | |
77 | def api_type(self): | |
78 | type_ = super().api_type | |
79 | if type_ == 'function': | |
80 | definition = self.tree_name.get_definition() | |
81 | if definition is None: | |
82 | return type_ | |
83 | if function_is_property(definition): | |
84 | # This essentially checks if there is an @property before | |
85 | # the function. @property could be something different, but | |
86 | # any programmer that redefines property as something that | |
87 | # is not really a property anymore, should be shot. (i.e. | |
88 | # this is a heuristic). | |
89 | return 'property' | |
90 | return type_ | |
91 | ||
92 | ||
93 | class ClassFilter(ParserTreeFilter): | |
94 | def __init__(self, class_value, node_context=None, until_position=None, | |
95 | origin_scope=None, is_instance=False): | |
96 | super().__init__( | |
97 | class_value.as_context(), node_context, | |
98 | until_position=until_position, | |
99 | origin_scope=origin_scope, | |
100 | ) | |
101 | self._class_value = class_value | |
102 | self._is_instance = is_instance | |
103 | ||
104 | def _convert_names(self, names): | |
105 | return [ | |
106 | ClassName( | |
107 | class_value=self._class_value, | |
108 | tree_name=name, | |
109 | name_context=self._node_context, | |
110 | apply_decorators=not self._is_instance, | |
111 | ) for name in names | |
112 | ] | |
113 | ||
114 | def _equals_origin_scope(self): | |
115 | node = self._origin_scope | |
116 | while node is not None: | |
117 | if node == self._parser_scope or node == self.parent_context: | |
118 | return True | |
119 | node = get_cached_parent_scope(self._parso_cache_node, node) | |
120 | return False | |
121 | ||
122 | def _access_possible(self, name): | |
123 | # Filter for name mangling of private variables like __foo | |
124 | return not name.value.startswith('__') or name.value.endswith('__') \ | |
125 | or self._equals_origin_scope() | |
126 | ||
127 | def _filter(self, names): | |
128 | names = super()._filter(names) | |
129 | return [name for name in names if self._access_possible(name)] | |
130 | ||
131 | ||
132 | class ClassMixin: | |
133 | def is_class(self): | |
134 | return True | |
135 | ||
136 | def is_class_mixin(self): | |
137 | return True | |
138 | ||
139 | def py__call__(self, arguments): | |
140 | from jedi.inference.value import TreeInstance | |
141 | ||
142 | from jedi.inference.gradual.typing import TypedDict | |
143 | if self.is_typeddict(): | |
144 | return ValueSet([TypedDict(self)]) | |
145 | return ValueSet([TreeInstance(self.inference_state, self.parent_context, self, arguments)]) | |
146 | ||
147 | def py__class__(self): | |
148 | return compiled.builtin_from_name(self.inference_state, 'type') | |
149 | ||
150 | @property | |
151 | def name(self): | |
152 | return ValueName(self, self.tree_node.name) | |
153 | ||
154 | def py__name__(self): | |
155 | return self.name.string_name | |
156 | ||
157 | @inference_state_method_generator_cache() | |
158 | def py__mro__(self): | |
159 | mro = [self] | |
160 | yield self | |
161 | # TODO Do a proper mro resolution. Currently we are just listing | |
162 | # classes. However, it's a complicated algorithm. | |
163 | for lazy_cls in self.py__bases__(): | |
164 | # TODO there's multiple different mro paths possible if this yields | |
165 | # multiple possibilities. Could be changed to be more correct. | |
166 | for cls in lazy_cls.infer(): | |
167 | # TODO detect for TypeError: duplicate base class str, | |
168 | # e.g. `class X(str, str): pass` | |
169 | try: | |
170 | mro_method = cls.py__mro__ | |
171 | except AttributeError: | |
172 | # TODO add a TypeError like: | |
173 | """ | |
174 | >>> class Y(lambda: test): pass | |
175 | Traceback (most recent call last): | |
176 | File "<stdin>", line 1, in <module> | |
177 | TypeError: function() argument 1 must be code, not str | |
178 | >>> class Y(1): pass | |
179 | Traceback (most recent call last): | |
180 | File "<stdin>", line 1, in <module> | |
181 | TypeError: int() takes at most 2 arguments (3 given) | |
182 | """ | |
183 | debug.warning('Super class of %s is not a class: %s', self, cls) | |
184 | else: | |
185 | for cls_new in mro_method(): | |
186 | if cls_new not in mro: | |
187 | mro.append(cls_new) | |
188 | yield cls_new | |
189 | ||
190 | def get_filters(self, origin_scope=None, is_instance=False, | |
191 | include_metaclasses=True, include_type_when_class=True): | |
192 | if include_metaclasses: | |
193 | metaclasses = self.get_metaclasses() | |
194 | if metaclasses: | |
195 | yield from self.get_metaclass_filters(metaclasses, is_instance) | |
196 | ||
197 | for cls in self.py__mro__(): | |
198 | if cls.is_compiled(): | |
199 | yield from cls.get_filters(is_instance=is_instance) | |
200 | else: | |
201 | yield ClassFilter( | |
202 | self, node_context=cls.as_context(), | |
203 | origin_scope=origin_scope, | |
204 | is_instance=is_instance | |
205 | ) | |
206 | if not is_instance and include_type_when_class: | |
207 | from jedi.inference.compiled import builtin_from_name | |
208 | type_ = builtin_from_name(self.inference_state, 'type') | |
209 | assert isinstance(type_, ClassValue) | |
210 | if type_ != self: | |
211 | # We are not using execute_with_values here, because the | |
212 | # plugin function for type would get executed instead of an | |
213 | # instance creation. | |
214 | args = ValuesArguments([]) | |
215 | for instance in type_.py__call__(args): | |
216 | instance_filters = instance.get_filters() | |
217 | # Filter out self filters | |
218 | next(instance_filters, None) | |
219 | next(instance_filters, None) | |
220 | x = next(instance_filters, None) | |
221 | assert x is not None | |
222 | yield x | |
223 | ||
224 | def get_signatures(self): | |
225 | # Since calling staticmethod without a function is illegal, the Jedi | |
226 | # plugin doesn't return anything. Therefore call directly and get what | |
227 | # we want: An instance of staticmethod. | |
228 | metaclasses = self.get_metaclasses() | |
229 | if metaclasses: | |
230 | sigs = self.get_metaclass_signatures(metaclasses) | |
231 | if sigs: | |
232 | return sigs | |
233 | args = ValuesArguments([]) | |
234 | init_funcs = self.py__call__(args).py__getattribute__('__init__') | |
235 | return [sig.bind(self) for sig in init_funcs.get_signatures()] | |
236 | ||
237 | def _as_context(self): | |
238 | return ClassContext(self) | |
239 | ||
240 | def get_type_hint(self, add_class_info=True): | |
241 | if add_class_info: | |
242 | return 'Type[%s]' % self.py__name__() | |
243 | return self.py__name__() | |
244 | ||
245 | @inference_state_method_cache(default=False) | |
246 | def is_typeddict(self): | |
247 | # TODO Do a proper mro resolution. Currently we are just listing | |
248 | # classes. However, it's a complicated algorithm. | |
249 | from jedi.inference.gradual.typing import TypedDictClass | |
250 | for lazy_cls in self.py__bases__(): | |
251 | if not isinstance(lazy_cls, LazyTreeValue): | |
252 | return False | |
253 | tree_node = lazy_cls.data | |
254 | # Only resolve simple classes, stuff like Iterable[str] are more | |
255 | # intensive to resolve and if generics are involved, we know it's | |
256 | # not a TypedDict. | |
257 | if not expr_is_dotted(tree_node): | |
258 | return False | |
259 | ||
260 | for cls in lazy_cls.infer(): | |
261 | if isinstance(cls, TypedDictClass): | |
262 | return True | |
263 | try: | |
264 | method = cls.is_typeddict | |
265 | except AttributeError: | |
266 | # We're only dealing with simple classes, so just returning | |
267 | # here should be fine. This only happens with e.g. compiled | |
268 | # classes. | |
269 | return False | |
270 | else: | |
271 | if method(): | |
272 | return True | |
273 | return False | |
274 | ||
275 | def py__getitem__(self, index_value_set, contextualized_node): | |
276 | from jedi.inference.gradual.base import GenericClass | |
277 | if not index_value_set: | |
278 | debug.warning('Class indexes inferred to nothing. Returning class instead') | |
279 | return ValueSet([self]) | |
280 | return ValueSet( | |
281 | GenericClass( | |
282 | self, | |
283 | LazyGenericManager( | |
284 | context_of_index=contextualized_node.context, | |
285 | index_value=index_value, | |
286 | ) | |
287 | ) | |
288 | for index_value in index_value_set | |
289 | ) | |
290 | ||
291 | def with_generics(self, generics_tuple): | |
292 | from jedi.inference.gradual.base import GenericClass | |
293 | return GenericClass( | |
294 | self, | |
295 | TupleGenericManager(generics_tuple) | |
296 | ) | |
297 | ||
298 | def define_generics(self, type_var_dict): | |
299 | from jedi.inference.gradual.base import GenericClass | |
300 | ||
301 | def remap_type_vars(): | |
302 | """ | |
303 | The TypeVars in the resulting classes have sometimes different names | |
304 | and we need to check for that, e.g. a signature can be: | |
305 | ||
306 | def iter(iterable: Iterable[_T]) -> Iterator[_T]: ... | |
307 | ||
308 | However, the iterator is defined as Iterator[_T_co], which means it has | |
309 | a different type var name. | |
310 | """ | |
311 | for type_var in self.list_type_vars(): | |
312 | yield type_var_dict.get(type_var.py__name__(), NO_VALUES) | |
313 | ||
314 | if type_var_dict: | |
315 | return ValueSet([GenericClass( | |
316 | self, | |
317 | TupleGenericManager(tuple(remap_type_vars())) | |
318 | )]) | |
319 | return ValueSet({self}) | |
320 | ||
321 | ||
322 | class ClassValue(ClassMixin, FunctionAndClassBase, metaclass=CachedMetaClass): | |
323 | api_type = 'class' | |
324 | ||
325 | @inference_state_method_cache() | |
326 | def list_type_vars(self): | |
327 | found = [] | |
328 | arglist = self.tree_node.get_super_arglist() | |
329 | if arglist is None: | |
330 | return [] | |
331 | ||
332 | for stars, node in unpack_arglist(arglist): | |
333 | if stars: | |
334 | continue # These are not relevant for this search. | |
335 | ||
336 | from jedi.inference.gradual.annotation import find_unknown_type_vars | |
337 | for type_var in find_unknown_type_vars(self.parent_context, node): | |
338 | if type_var not in found: | |
339 | # The order matters and it's therefore a list. | |
340 | found.append(type_var) | |
341 | return found | |
342 | ||
343 | def _get_bases_arguments(self): | |
344 | arglist = self.tree_node.get_super_arglist() | |
345 | if arglist: | |
346 | from jedi.inference import arguments | |
347 | return arguments.TreeArguments(self.inference_state, self.parent_context, arglist) | |
348 | return None | |
349 | ||
350 | @inference_state_method_cache(default=()) | |
351 | def py__bases__(self): | |
352 | args = self._get_bases_arguments() | |
353 | if args is not None: | |
354 | lst = [value for key, value in args.unpack() if key is None] | |
355 | if lst: | |
356 | return lst | |
357 | ||
358 | if self.py__name__() == 'object' \ | |
359 | and self.parent_context.is_builtins_module(): | |
360 | return [] | |
361 | return [LazyKnownValues( | |
362 | self.inference_state.builtins_module.py__getattribute__('object') | |
363 | )] | |
364 | ||
365 | @plugin_manager.decorate() | |
366 | def get_metaclass_filters(self, metaclasses, is_instance): | |
367 | debug.warning('Unprocessed metaclass %s', metaclasses) | |
368 | return [] | |
369 | ||
370 | @inference_state_method_cache(default=NO_VALUES) | |
371 | def get_metaclasses(self): | |
372 | args = self._get_bases_arguments() | |
373 | if args is not None: | |
374 | m = [value for key, value in args.unpack() if key == 'metaclass'] | |
375 | metaclasses = ValueSet.from_sets(lazy_value.infer() for lazy_value in m) | |
376 | metaclasses = ValueSet(m for m in metaclasses if m.is_class()) | |
377 | if metaclasses: | |
378 | return metaclasses | |
379 | ||
380 | for lazy_base in self.py__bases__(): | |
381 | for value in lazy_base.infer(): | |
382 | if value.is_class(): | |
383 | values = value.get_metaclasses() | |
384 | if values: | |
385 | return values | |
386 | return NO_VALUES | |
387 | ||
388 | @plugin_manager.decorate() | |
389 | def get_metaclass_signatures(self, metaclasses): | |
390 | return [] |