1"""
2Like described in the :mod:`parso.python.tree` module,
3there's a need for an ast like module to represent the states of parsed
4modules.
5
6But now there are also structures in Python that need a little bit more than
7that. An ``Instance`` for example is only a ``Class`` before it is
8instantiated. This class represents these cases.
9
10So, why is there also a ``Class`` class here? Well, there are decorators and
11they change classes in Python 3.
12
13Representation modules also define "magic methods". Those methods look like
14``py__foo__`` and are typically mappable to the Python equivalents ``__call__``
15and others. Here's a list:
16
17====================================== ========================================
18**Method** **Description**
19-------------------------------------- ----------------------------------------
20py__call__(arguments: Array) On callable objects, returns types.
21py__bool__() Returns True/False/None; None means that
22 there's no certainty.
23py__bases__() Returns a list of base classes.
24py__iter__() Returns a generator of a set of types.
25py__class__() Returns the class of an instance.
26py__simple_getitem__(index: int/str) Returns a a set of types of the index.
27 Can raise an IndexError/KeyError.
28py__getitem__(indexes: ValueSet) Returns a a set of types of the index.
29py__file__() Only on modules. Returns None if does
30 not exist.
31py__package__() -> List[str] Only on modules. For the import system.
32py__path__() Only on modules. For the import system.
33py__get__(call_object) Only on instances. Simulates
34 descriptors.
35py__doc__() Returns the docstring for a value.
36====================================== ========================================
37
38"""
39from __future__ import annotations
40
41from typing import List, Optional, Tuple
42
43from jedi import debug
44from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted, \
45 function_is_property
46from jedi.inference.cache import inference_state_method_cache, CachedMetaClass, \
47 inference_state_method_generator_cache
48from jedi.inference import compiled
49from jedi.inference.lazy_value import LazyKnownValues, LazyTreeValue
50from jedi.inference.filters import ParserTreeFilter
51from jedi.inference.names import TreeNameDefinition, ValueName
52from jedi.inference.arguments import unpack_arglist, ValuesArguments
53from jedi.inference.base_value import ValueSet, iterator_to_value_set, \
54 NO_VALUES, ValueWrapper
55from jedi.inference.context import ClassContext
56from jedi.inference.value.function import FunctionAndClassBase, FunctionMixin
57from jedi.inference.value.decorator import Decoratee
58from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager
59from jedi.plugins import plugin_manager
60from inspect import Parameter
61from jedi.inference.names import BaseTreeParamName
62from jedi.inference.signature import AbstractSignature
63
64
65class ClassName(TreeNameDefinition):
66 def __init__(self, class_value, tree_name, name_context, apply_decorators):
67 super().__init__(name_context, tree_name)
68 self._apply_decorators = apply_decorators
69 self._class_value = class_value
70
71 @iterator_to_value_set
72 def infer(self):
73 # We're using a different value to infer, so we cannot call super().
74 from jedi.inference.syntax_tree import tree_name_to_values
75 inferred = tree_name_to_values(
76 self.parent_context.inference_state, self.parent_context, self.tree_name)
77
78 for result_value in inferred:
79 if self._apply_decorators:
80 yield from result_value.py__get__(instance=None, class_value=self._class_value)
81 else:
82 yield result_value
83
84 @property
85 def api_type(self):
86 type_ = super().api_type
87 if type_ == 'function':
88 definition = self.tree_name.get_definition()
89 if definition is None:
90 return type_
91 if function_is_property(definition):
92 # This essentially checks if there is an @property before
93 # the function. @property could be something different, but
94 # any programmer that redefines property as something that
95 # is not really a property anymore, should be shot. (i.e.
96 # this is a heuristic).
97 return 'property'
98 return type_
99
100
101class ClassFilter(ParserTreeFilter):
102 def __init__(self, class_value, node_context=None, until_position=None,
103 origin_scope=None, is_instance=False):
104 super().__init__(
105 class_value.as_context(), node_context,
106 until_position=until_position,
107 origin_scope=origin_scope,
108 )
109 self._class_value = class_value
110 self._is_instance = is_instance
111
112 def _convert_names(self, names):
113 return [
114 ClassName(
115 class_value=self._class_value,
116 tree_name=name,
117 name_context=self._node_context,
118 apply_decorators=not self._is_instance,
119 ) for name in names
120 ]
121
122 def _equals_origin_scope(self):
123 node = self._origin_scope
124 while node is not None:
125 if node == self._parser_scope or node == self.parent_context:
126 return True
127 node = get_cached_parent_scope(self._parso_cache_node, node)
128 return False
129
130 def _access_possible(self, name):
131 # Filter for name mangling of private variables like __foo
132 return not name.value.startswith('__') or name.value.endswith('__') \
133 or self._equals_origin_scope()
134
135 def _filter(self, names):
136 names = super()._filter(names)
137 return [name for name in names if self._access_possible(name)]
138
139
140def init_param_value(arg_nodes) -> Optional[bool]:
141 """
142 Returns:
143
144 - ``True`` if ``@dataclass(init=True)``
145 - ``False`` if ``@dataclass(init=False)``
146 - ``None`` if not specified ``@dataclass()``
147 """
148 for arg_node in arg_nodes:
149 if (
150 arg_node.type == "argument"
151 and arg_node.children[0].value == "init"
152 ):
153 if arg_node.children[2].value == "False":
154 return False
155 elif arg_node.children[2].value == "True":
156 return True
157
158 return None
159
160
161def get_dataclass_param_names(cls) -> List[DataclassParamName]:
162 """
163 ``cls`` is a :class:`ClassMixin`. The type is only documented as mypy would
164 complain that some fields are missing.
165
166 .. code:: python
167
168 @dataclass
169 class A:
170 a: int
171 b: str = "toto"
172
173 For the previous example, the param names would be ``a`` and ``b``.
174 """
175 param_names = []
176 filter_ = cls.as_context().get_global_filter()
177 for name in sorted(filter_.values(), key=lambda name: name.start_pos):
178 d = name.tree_name.get_definition()
179 annassign = d.children[1]
180 if d.type == 'expr_stmt' and annassign.type == 'annassign':
181 node = annassign.children[1]
182 if node.type == "atom_expr" and node.children[0].value == "ClassVar":
183 continue
184
185 if len(annassign.children) < 4:
186 default = None
187 else:
188 default = annassign.children[3]
189
190 param_names.append(DataclassParamName(
191 parent_context=cls.parent_context,
192 tree_name=name.tree_name,
193 annotation_node=annassign.children[1],
194 default_node=default,
195 ))
196 return param_names
197
198
199class ClassMixin:
200 def is_class(self):
201 return True
202
203 def is_class_mixin(self):
204 return True
205
206 def py__call__(self, arguments):
207 from jedi.inference.value import TreeInstance
208
209 from jedi.inference.gradual.typing import TypedDict
210 if self.is_typeddict():
211 return ValueSet([TypedDict(self)])
212 return ValueSet([TreeInstance(self.inference_state, self.parent_context, self, arguments)])
213
214 def py__class__(self):
215 return compiled.builtin_from_name(self.inference_state, 'type')
216
217 @property
218 def name(self):
219 return ValueName(self, self.tree_node.name)
220
221 def py__name__(self):
222 return self.name.string_name
223
224 @inference_state_method_generator_cache()
225 def py__mro__(self):
226 mro = [self]
227 yield self
228 # TODO Do a proper mro resolution. Currently we are just listing
229 # classes. However, it's a complicated algorithm.
230 for lazy_cls in self.py__bases__():
231 # TODO there's multiple different mro paths possible if this yields
232 # multiple possibilities. Could be changed to be more correct.
233 for cls in lazy_cls.infer():
234 # TODO detect for TypeError: duplicate base class str,
235 # e.g. `class X(str, str): pass`
236 try:
237 mro_method = cls.py__mro__
238 except AttributeError:
239 # TODO add a TypeError like:
240 """
241 >>> class Y(lambda: test): pass
242 Traceback (most recent call last):
243 File "<stdin>", line 1, in <module>
244 TypeError: function() argument 1 must be code, not str
245 >>> class Y(1): pass
246 Traceback (most recent call last):
247 File "<stdin>", line 1, in <module>
248 TypeError: int() takes at most 2 arguments (3 given)
249 """
250 debug.warning('Super class of %s is not a class: %s', self, cls)
251 else:
252 for cls_new in mro_method():
253 if cls_new not in mro:
254 mro.append(cls_new)
255 yield cls_new
256
257 def get_filters(self, origin_scope=None, is_instance=False,
258 include_metaclasses=True, include_type_when_class=True):
259 if include_metaclasses:
260 metaclasses = self.get_metaclasses()
261 if metaclasses:
262 yield from self.get_metaclass_filters(metaclasses, is_instance)
263
264 for cls in self.py__mro__():
265 if cls.is_compiled():
266 yield from cls.get_filters(is_instance=is_instance)
267 else:
268 yield ClassFilter(
269 self, node_context=cls.as_context(),
270 origin_scope=origin_scope,
271 is_instance=is_instance
272 )
273 if not is_instance and include_type_when_class:
274 from jedi.inference.compiled import builtin_from_name
275 type_ = builtin_from_name(self.inference_state, 'type')
276 assert isinstance(type_, ClassValue)
277 if type_ != self:
278 # We are not using execute_with_values here, because the
279 # plugin function for type would get executed instead of an
280 # instance creation.
281 args = ValuesArguments([])
282 for instance in type_.py__call__(args):
283 instance_filters = instance.get_filters()
284 # Filter out self filters
285 next(instance_filters, None)
286 next(instance_filters, None)
287 x = next(instance_filters, None)
288 assert x is not None
289 yield x
290
291 def _has_dataclass_transform_metaclasses(self) -> Tuple[bool, Optional[bool]]:
292 for meta in self.get_metaclasses(): # type: ignore[attr-defined]
293 if (
294 isinstance(meta, Decoratee)
295 # Internal leakage :|
296 and isinstance(meta._wrapped_value, DataclassTransformer)
297 ):
298 return True, meta._wrapped_value.init_mode_from_new()
299
300 return False, None
301
302 def _get_dataclass_transform_signatures(self) -> List[DataclassSignature]:
303 """
304 Returns: A non-empty list if the class has dataclass semantics else an
305 empty list.
306
307 The dataclass-like semantics will be assumed for any class that directly
308 or indirectly derives from the decorated class or uses the decorated
309 class as a metaclass.
310 """
311 param_names = []
312 is_dataclass_transform = False
313 default_init_mode: Optional[bool] = None
314 for cls in reversed(list(self.py__mro__())):
315 if not is_dataclass_transform:
316
317 # If dataclass_transform is applied to a class, dataclass-like semantics
318 # will be assumed for any class that directly or indirectly derives from
319 # the decorated class or uses the decorated class as a metaclass.
320 if (
321 isinstance(cls, DataclassTransformer)
322 and cls.init_mode_from_init_subclass
323 ):
324 is_dataclass_transform = True
325 default_init_mode = cls.init_mode_from_init_subclass
326
327 elif (
328 # Some object like CompiledValues would not be compatible
329 isinstance(cls, ClassMixin)
330 ):
331 is_dataclass_transform, default_init_mode = (
332 cls._has_dataclass_transform_metaclasses()
333 )
334
335 # Attributes on the decorated class and its base classes are not
336 # considered to be fields.
337 if is_dataclass_transform:
338 continue
339
340 # All inherited classes behave like dataclass semantics
341 if (
342 is_dataclass_transform
343 and isinstance(cls, ClassValue)
344 and (
345 cls.init_param_mode()
346 or (cls.init_param_mode() is None and default_init_mode)
347 )
348 ):
349 param_names.extend(
350 get_dataclass_param_names(cls)
351 )
352
353 if is_dataclass_transform:
354 return [DataclassSignature(cls, param_names)]
355 else:
356 return []
357
358 def get_signatures(self):
359 # Since calling staticmethod without a function is illegal, the Jedi
360 # plugin doesn't return anything. Therefore call directly and get what
361 # we want: An instance of staticmethod.
362 metaclasses = self.get_metaclasses()
363 if metaclasses:
364 sigs = self.get_metaclass_signatures(metaclasses)
365 if sigs:
366 return sigs
367 args = ValuesArguments([])
368 init_funcs = self.py__call__(args).py__getattribute__('__init__')
369
370 dataclass_sigs = self._get_dataclass_transform_signatures()
371 if dataclass_sigs:
372 return dataclass_sigs
373 else:
374 return [sig.bind(self) for sig in init_funcs.get_signatures()]
375
376 def _as_context(self):
377 return ClassContext(self)
378
379 def get_type_hint(self, add_class_info=True):
380 if add_class_info:
381 return 'Type[%s]' % self.py__name__()
382 return self.py__name__()
383
384 @inference_state_method_cache(default=False)
385 def is_typeddict(self):
386 # TODO Do a proper mro resolution. Currently we are just listing
387 # classes. However, it's a complicated algorithm.
388 from jedi.inference.gradual.typing import TypedDictClass
389 for lazy_cls in self.py__bases__():
390 if not isinstance(lazy_cls, LazyTreeValue):
391 return False
392 tree_node = lazy_cls.data
393 # Only resolve simple classes, stuff like Iterable[str] are more
394 # intensive to resolve and if generics are involved, we know it's
395 # not a TypedDict.
396 if not expr_is_dotted(tree_node):
397 return False
398
399 for cls in lazy_cls.infer():
400 if isinstance(cls, TypedDictClass):
401 return True
402 try:
403 method = cls.is_typeddict
404 except AttributeError:
405 # We're only dealing with simple classes, so just returning
406 # here should be fine. This only happens with e.g. compiled
407 # classes.
408 return False
409 else:
410 if method():
411 return True
412 return False
413
414 def py__getitem__(self, index_value_set, contextualized_node):
415 from jedi.inference.gradual.base import GenericClass
416 if not index_value_set:
417 debug.warning('Class indexes inferred to nothing. Returning class instead')
418 return ValueSet([self])
419 return ValueSet(
420 GenericClass(
421 self,
422 LazyGenericManager(
423 context_of_index=contextualized_node.context,
424 index_value=index_value,
425 )
426 )
427 for index_value in index_value_set
428 )
429
430 def with_generics(self, generics_tuple):
431 from jedi.inference.gradual.base import GenericClass
432 return GenericClass(
433 self,
434 TupleGenericManager(generics_tuple)
435 )
436
437 def define_generics(self, type_var_dict):
438 from jedi.inference.gradual.base import GenericClass
439
440 def remap_type_vars():
441 """
442 The TypeVars in the resulting classes have sometimes different names
443 and we need to check for that, e.g. a signature can be:
444
445 def iter(iterable: Iterable[_T]) -> Iterator[_T]: ...
446
447 However, the iterator is defined as Iterator[_T_co], which means it has
448 a different type var name.
449 """
450 for type_var in self.list_type_vars():
451 yield type_var_dict.get(type_var.py__name__(), NO_VALUES)
452
453 if type_var_dict:
454 return ValueSet([GenericClass(
455 self,
456 TupleGenericManager(tuple(remap_type_vars()))
457 )])
458 return ValueSet({self})
459
460
461class DataclassParamName(BaseTreeParamName):
462 """
463 Represent a field declaration on a class with dataclass semantics.
464 """
465
466 def __init__(self, parent_context, tree_name, annotation_node, default_node):
467 super().__init__(parent_context, tree_name)
468 self.annotation_node = annotation_node
469 self.default_node = default_node
470
471 def get_kind(self):
472 return Parameter.POSITIONAL_OR_KEYWORD
473
474 def infer(self):
475 if self.annotation_node is None:
476 return NO_VALUES
477 else:
478 return self.parent_context.infer_node(self.annotation_node)
479
480
481class DataclassSignature(AbstractSignature):
482 """
483 It represents the ``__init__`` signature of a class with dataclass semantics.
484
485 .. code:: python
486
487 """
488 def __init__(self, value, param_names):
489 super().__init__(value)
490 self._param_names = param_names
491
492 def get_param_names(self, resolve_stars=False):
493 return self._param_names
494
495
496class DataclassDecorator(ValueWrapper, FunctionMixin):
497 """
498 A dataclass(-like) decorator with custom parameters.
499
500 .. code:: python
501
502 @dataclass(init=True) # this
503 class A: ...
504
505 @dataclass_transform
506 def create_model(*, init=False): pass
507
508 @create_model(init=False) # or this
509 class B: ...
510 """
511
512 def __init__(self, function, arguments, default_init: bool = True):
513 """
514 Args:
515 function: Decoratee | function
516 arguments: The parameters to the dataclass function decorator
517 default_init: Boolean to indicate the default init value
518 """
519 super().__init__(function)
520 argument_init = self._init_param_value(arguments)
521 self.init_param_mode = (
522 argument_init if argument_init is not None else default_init
523 )
524
525 def _init_param_value(self, arguments) -> Optional[bool]:
526 if not arguments.argument_node:
527 return None
528
529 arg_nodes = (
530 arguments.argument_node.children
531 if arguments.argument_node.type == "arglist"
532 else [arguments.argument_node]
533 )
534
535 return init_param_value(arg_nodes)
536
537
538class DataclassTransformer(ValueWrapper, ClassMixin):
539 """
540 A class decorated with the ``dataclass_transform`` decorator. dataclass-like
541 semantics will be assumed for any class that directly or indirectly derives
542 from the decorated class or uses the decorated class as a metaclass.
543 Attributes on the decorated class and its base classes are not considered to
544 be fields.
545 """
546 def __init__(self, wrapped_value):
547 super().__init__(wrapped_value)
548
549 def init_mode_from_new(self) -> bool:
550 """Default value if missing is ``True``"""
551 new_methods = self._wrapped_value.py__getattribute__("__new__")
552
553 if not new_methods:
554 return True
555
556 new_method = list(new_methods)[0]
557
558 for param in new_method.get_param_names():
559 if (
560 param.string_name == "init"
561 and param.default_node
562 and param.default_node.type == "keyword"
563 ):
564 if param.default_node.value == "False":
565 return False
566 elif param.default_node.value == "True":
567 return True
568
569 return True
570
571 @property
572 def init_mode_from_init_subclass(self) -> Optional[bool]:
573 # def __init_subclass__(cls) -> None: ... is hardcoded in the typeshed
574 # so the extra parameters can not be inferred.
575 return True
576
577
578class DataclassWrapper(ValueWrapper, ClassMixin):
579 """
580 A class with dataclass semantics from a decorator. The init parameters are
581 only from the current class and parent classes decorated where the ``init``
582 parameter was ``True``.
583
584 .. code:: python
585
586 @dataclass
587 class A: ... # this
588
589 @dataclass_transform
590 def create_model(): pass
591
592 @create_model()
593 class B: ... # or this
594 """
595
596 def __init__(
597 self, wrapped_value, should_generate_init: bool
598 ):
599 super().__init__(wrapped_value)
600 self.should_generate_init = should_generate_init
601
602 def get_signatures(self):
603 param_names = []
604 for cls in reversed(list(self.py__mro__())):
605 if (
606 isinstance(cls, DataclassWrapper)
607 and cls.should_generate_init
608 ):
609 param_names.extend(get_dataclass_param_names(cls))
610 return [DataclassSignature(cls, param_names)]
611
612
613class ClassValue(ClassMixin, FunctionAndClassBase, metaclass=CachedMetaClass):
614 api_type = 'class'
615
616 @inference_state_method_cache()
617 def list_type_vars(self):
618 found = []
619 arglist = self.tree_node.get_super_arglist()
620 if arglist is None:
621 return []
622
623 for stars, node in unpack_arglist(arglist):
624 if stars:
625 continue # These are not relevant for this search.
626
627 from jedi.inference.gradual.annotation import find_unknown_type_vars
628 for type_var in find_unknown_type_vars(self.parent_context, node):
629 if type_var not in found:
630 # The order matters and it's therefore a list.
631 found.append(type_var)
632 return found
633
634 def _get_bases_arguments(self):
635 arglist = self.tree_node.get_super_arglist()
636 if arglist:
637 from jedi.inference import arguments
638 return arguments.TreeArguments(self.inference_state, self.parent_context, arglist)
639 return None
640
641 @inference_state_method_cache(default=())
642 def py__bases__(self):
643 args = self._get_bases_arguments()
644 if args is not None:
645 lst = [value for key, value in args.unpack() if key is None]
646 if lst:
647 return lst
648
649 if self.py__name__() == 'object' \
650 and self.parent_context.is_builtins_module():
651 return []
652 return [LazyKnownValues(
653 self.inference_state.builtins_module.py__getattribute__('object')
654 )]
655
656 @plugin_manager.decorate()
657 def get_metaclass_filters(self, metaclasses, is_instance):
658 debug.warning('Unprocessed metaclass %s', metaclasses)
659 return []
660
661 @inference_state_method_cache(default=NO_VALUES)
662 def get_metaclasses(self):
663 args = self._get_bases_arguments()
664 if args is not None:
665 m = [value for key, value in args.unpack() if key == 'metaclass']
666 metaclasses = ValueSet.from_sets(lazy_value.infer() for lazy_value in m)
667 metaclasses = ValueSet(m for m in metaclasses if m.is_class())
668 if metaclasses:
669 return metaclasses
670
671 for lazy_base in self.py__bases__():
672 for value in lazy_base.infer():
673 if value.is_class():
674 values = value.get_metaclasses()
675 if values:
676 return values
677 return NO_VALUES
678
679 def init_param_mode(self) -> Optional[bool]:
680 """
681 It returns ``True`` if ``class X(init=False):`` else ``False``.
682 """
683 bases_arguments = self._get_bases_arguments()
684
685 if bases_arguments.argument_node.type != "arglist":
686 # If it is not inheriting from the base model and having
687 # extra parameters, then init behavior is not changed.
688 return None
689
690 return init_param_value(bases_arguments.argument_node.children)
691
692 @plugin_manager.decorate()
693 def get_metaclass_signatures(self, metaclasses):
694 return []