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