]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | import copy |
2 | import sys | |
3 | import re | |
4 | import os | |
5 | from itertools import chain | |
6 | from contextlib import contextmanager | |
7 | ||
8 | from parso.python import tree | |
9 | ||
10 | ||
11 | def is_stdlib_path(path): | |
12 | # Python standard library paths look like this: | |
13 | # /usr/lib/python3.9/... | |
14 | # TODO The implementation below is probably incorrect and not complete. | |
15 | parts = path.parts | |
16 | if 'dist-packages' in parts or 'site-packages' in parts: | |
17 | return False | |
18 | ||
19 | base_path = os.path.join(sys.prefix, 'lib', 'python') | |
20 | return bool(re.match(re.escape(base_path) + r'\d.\d', str(path))) | |
21 | ||
22 | ||
23 | def deep_ast_copy(obj): | |
24 | """ | |
25 | Much, much faster than copy.deepcopy, but just for parser tree nodes. | |
26 | """ | |
27 | # If it's already in the cache, just return it. | |
28 | new_obj = copy.copy(obj) | |
29 | ||
30 | # Copy children | |
31 | new_children = [] | |
32 | for child in obj.children: | |
33 | if isinstance(child, tree.Leaf): | |
34 | new_child = copy.copy(child) | |
35 | new_child.parent = new_obj | |
36 | else: | |
37 | new_child = deep_ast_copy(child) | |
38 | new_child.parent = new_obj | |
39 | new_children.append(new_child) | |
40 | new_obj.children = new_children | |
41 | ||
42 | return new_obj | |
43 | ||
44 | ||
45 | def infer_call_of_leaf(context, leaf, cut_own_trailer=False): | |
46 | """ | |
47 | Creates a "call" node that consist of all ``trailer`` and ``power`` | |
48 | objects. E.g. if you call it with ``append``:: | |
49 | ||
50 | list([]).append(3) or None | |
51 | ||
52 | You would get a node with the content ``list([]).append`` back. | |
53 | ||
54 | This generates a copy of the original ast node. | |
55 | ||
56 | If you're using the leaf, e.g. the bracket `)` it will return ``list([])``. | |
57 | ||
58 | We use this function for two purposes. Given an expression ``bar.foo``, | |
59 | we may want to | |
60 | - infer the type of ``foo`` to offer completions after foo | |
61 | - infer the type of ``bar`` to be able to jump to the definition of foo | |
62 | The option ``cut_own_trailer`` must be set to true for the second purpose. | |
63 | """ | |
64 | trailer = leaf.parent | |
65 | if trailer.type == 'fstring': | |
66 | from jedi.inference import compiled | |
67 | return compiled.get_string_value_set(context.inference_state) | |
68 | ||
69 | # The leaf may not be the last or first child, because there exist three | |
70 | # different trailers: `( x )`, `[ x ]` and `.x`. In the first two examples | |
71 | # we should not match anything more than x. | |
72 | if trailer.type != 'trailer' or leaf not in (trailer.children[0], trailer.children[-1]): | |
73 | if leaf == ':': | |
74 | # Basically happens with foo[:] when the cursor is on the colon | |
75 | from jedi.inference.base_value import NO_VALUES | |
76 | return NO_VALUES | |
77 | if trailer.type == 'atom': | |
78 | return context.infer_node(trailer) | |
79 | return context.infer_node(leaf) | |
80 | ||
81 | power = trailer.parent | |
82 | index = power.children.index(trailer) | |
83 | if cut_own_trailer: | |
84 | cut = index | |
85 | else: | |
86 | cut = index + 1 | |
87 | ||
88 | if power.type == 'error_node': | |
89 | start = index | |
90 | while True: | |
91 | start -= 1 | |
92 | base = power.children[start] | |
93 | if base.type != 'trailer': | |
94 | break | |
95 | trailers = power.children[start + 1:cut] | |
96 | else: | |
97 | base = power.children[0] | |
98 | trailers = power.children[1:cut] | |
99 | ||
100 | if base == 'await': | |
101 | base = trailers[0] | |
102 | trailers = trailers[1:] | |
103 | ||
104 | values = context.infer_node(base) | |
105 | from jedi.inference.syntax_tree import infer_trailer | |
106 | for trailer in trailers: | |
107 | values = infer_trailer(context, values, trailer) | |
108 | return values | |
109 | ||
110 | ||
111 | def get_names_of_node(node): | |
112 | try: | |
113 | children = node.children | |
114 | except AttributeError: | |
115 | if node.type == 'name': | |
116 | return [node] | |
117 | else: | |
118 | return [] | |
119 | else: | |
120 | return list(chain.from_iterable(get_names_of_node(c) for c in children)) | |
121 | ||
122 | ||
123 | def is_string(value): | |
124 | return value.is_compiled() and isinstance(value.get_safe_value(default=None), str) | |
125 | ||
126 | ||
127 | def is_literal(value): | |
128 | return is_number(value) or is_string(value) | |
129 | ||
130 | ||
131 | def _get_safe_value_or_none(value, accept): | |
132 | value = value.get_safe_value(default=None) | |
133 | if isinstance(value, accept): | |
134 | return value | |
135 | ||
136 | ||
137 | def get_int_or_none(value): | |
138 | return _get_safe_value_or_none(value, int) | |
139 | ||
140 | ||
141 | def get_str_or_none(value): | |
142 | return _get_safe_value_or_none(value, str) | |
143 | ||
144 | ||
145 | def is_number(value): | |
146 | return _get_safe_value_or_none(value, (int, float)) is not None | |
147 | ||
148 | ||
149 | class SimpleGetItemNotFound(Exception): | |
150 | pass | |
151 | ||
152 | ||
153 | @contextmanager | |
154 | def reraise_getitem_errors(*exception_classes): | |
155 | try: | |
156 | yield | |
157 | except exception_classes as e: | |
158 | raise SimpleGetItemNotFound(e) | |
159 | ||
160 | ||
161 | def parse_dotted_names(nodes, is_import_from, until_node=None): | |
162 | level = 0 | |
163 | names = [] | |
164 | for node in nodes[1:]: | |
165 | if node in ('.', '...'): | |
166 | if not names: | |
167 | level += len(node.value) | |
168 | elif node.type == 'dotted_name': | |
169 | for n in node.children[::2]: | |
170 | names.append(n) | |
171 | if n is until_node: | |
172 | break | |
173 | else: | |
174 | continue | |
175 | break | |
176 | elif node.type == 'name': | |
177 | names.append(node) | |
178 | if node is until_node: | |
179 | break | |
180 | elif node == ',': | |
181 | if not is_import_from: | |
182 | names = [] | |
183 | else: | |
184 | # Here if the keyword `import` comes along it stops checking | |
185 | # for names. | |
186 | break | |
187 | return level, names | |
188 | ||
189 | ||
190 | def values_from_qualified_names(inference_state, *names): | |
191 | return inference_state.import_module(names[:-1]).py__getattribute__(names[-1]) | |
192 | ||
193 | ||
194 | def is_big_annoying_library(context): | |
195 | string_names = context.get_root_context().string_names | |
196 | if string_names is None: | |
197 | return False | |
198 | ||
199 | # Especially pandas and tensorflow are huge complicated Python libraries | |
200 | # that get even slower than they already are when Jedi tries to undrstand | |
201 | # dynamic features like decorators, ifs and other stuff. | |
202 | return string_names[0] in ('pandas', 'numpy', 'tensorflow', 'matplotlib') |